package com.sunseaypw.console;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.rmi.activation.ActivationException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.text.Annotation;
import java.text.SimpleDateFormat;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Deque;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.Properties;
import java.util.Queue;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.activation.DataHandler;
import javax.crypto.Cipher;
import javax.mail.Address;
import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Transport;
import javax.mail.URLName;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.search.SearchTerm;
import javax.mail.search.SubjectTerm;
import javax.mail.util.ByteArrayDataSource;
import javax.management.RuntimeErrorException;
import javax.sql.DataSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import com.sunseaypw.base.BizException;
import com.sunseaypw.base.MyLogger;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import org.apache.log4j.pattern.IntegerPatternConverter;
import org.bouncycastle.crypto.RuntimeCryptoException;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Hello world!
 *
 */
public class App
{ 
    public static void main( String[] args )
    {        
        MyLogger.info("开始进来了","main");
        System.out.println( "Hello World!" );
        var c =new App();
        if(c==null){
            c.baseVar1();
            c.baseVar2();
            c.baseVar3();
            c.baseCompute();
            c.bitShift();
            c.floatCompute();
            c.charStrHandle();
            c.arrayHandle();
            c.consoleInputFormat();
            c.switchHandle();
            c.argsFunction(22);
            c.argsFunction(33,"222","333");
            c.instanceofHandle();
            c.polymorphicHandle();
            c.defaultMethodHandle();
            c.stringHandle();
            c.boxingHandle();
            c.beanInfoHandle();
            c.enumHandle();
            c.mathToolHandle();
            c.throwableHandle();
            c.testOptional();
            c.datetimeOldAbout();
            c.datetimeNewAbout();
            c.datetimeNewAbout2();
        }
        if(c==null){
            c.regexHandle();   
            c.reflectHandle();
            c.reflectHandle2();
            c.reflexHandle3();
            c.interfaceObject();
            c.checkByAnnotations();
            c.genericHandle();
            c.genericHandle2();
            c.genericHandle3();
            var aa=new StaticGeneric<Integer,Number>(12333);
            c.genericHandle4(aa);
            c.genericHandle5(aa);
            c.genericHandle6();
            c.anonymousHandler2();
        }        
        if(c==null){
            c.collectionHandle();
            c.collectionHandle2();
            c.collectionHandle3();
            c.collectionHandle4();
            c.collectionHandle5();
            c.collectionHandle6();
            c.collectionHandle7();
            c.collectionHandle8();
            c.collectionHandle9();
            c.collectionHandle91();
        }
        if(c==null){
            c.ioHandle();
            c.ioHandle2();
            c.ioHandle3();
            c.ioHandle4();
            c.ioHandle5();
            c.ioHandle6();
            c.ioHandle7();
            c.ioHandle8();
            c.ioHandle9();
            c.ioHandle91();
        }
        if(c==null){
            c.URLEncode();
            c.thirdCyprolib();
            c.cyproHandle();
            c.digitSigner();
            c.digitCertificate();
        }
        if(c==null){
            // System.out.println("开始子线程threadHandle1");
            // c.threadHandle1();
            // System.out.println("结束子线程threadHandle1");
            //c.threadHandle2();
            //c.threadHandle3();
            //c.threadHandle4();
            //c.threadHandle5();
            //c.threadHandle6();
            //c.threadHandle7();
            //c.threadHandle8();
            //c.threadHandle9();
            //c.threadHandle91();
            //c.threadHandle92();
            //c.threadHandle93();
            //c.threadHandle94();
            //c.threadHandle95();
            //c.threadHandle96();
            //c.threadHandle97();
        }        
        if(c==null){
            //c.threadLocalTest();
            //c.forkjoinTest();
            //c.futureMockTest();
            //c.completableFutureTest2();
        }
        if(c==null){
            //c.tcpServerHandle1();
            //c.tcpClientHandle1();
            // c.udpserverHandle1();
            // c.udpClientHandle1();
        }
        if(c==null){
            //c.sendemailTest();
            //c.recieveMailTest();
        }
        if(c==null){
            //c.httpclientTest();
            //c.rmiServerTest();
            //c.rmiClientTest();
        }
        if(c==null){
            //c.xmlTest();
            //c.jsonHandler();
            //c.jdbcHandler();
        }
        if(c==null){
            //c.funcProgTest();
            //c.forkjoinTest2();
            c.steamAboutTest();
        }
        if(c!=null){
            c.designModeCreate();
            c.designmodeStruct();
            c.designmodeAction();
        }
        MyLogger.warn("结束了","main2222");
    }
    //行为型模式主要涉及算法和对象间的职责分配。通过对象组合，行为型模式可以描述一组对象应该如何协作来完成一个整体任务
    public void designmodeAction(){
        try{
            var fileObj=new DiretoryScan(new File("."));
            fileObj.scan(new FileVistor(){
                public void visitFile(File file){
                    System.out.println("visitFile:"+file.getAbsolutePath());
                }
                public void visitDiretory(File file){
                    System.out.println("visitDiretory:"+file.getAbsolutePath());
                }
            });
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }
    //访问者模式先把数据结构（这里是文件夹和文件构成的树型结构）和对其的操作（查找文件）分离开，
    //   以后如果要新增操作（例如清理.class文件），只需要新增访问者，不需要改变现有逻辑。
    interface FileVistor{
        void visitFile(File file);
        void visitDiretory(File file);
    }
    static public class DiretoryScan{
        File file;
        public DiretoryScan(File file){
            this.file=file;
        }
        public void scan(FileVistor visitor) {//暴露给外部
            this.scan(this.file, visitor);
        }
        //可以在同一套数据处理逻辑下对不同访问者提供访问回调服务
        private void scan(File file, FileVistor visitor) {//内部处理
            if (file.isDirectory()) {            
                visitor.visitDiretory(file);// 让访问者处理文件夹:
                for (File sub : file.listFiles()) {                    
                    scan(sub, visitor);// 递归处理子文件夹:
                }
            } else if (file.isFile()) {// 让访问者处理文件:                
                visitor.visitFile(file);
            }
        }
    }

    //结构型设计模式：通过组合来扩充功能 适配/桥接/门面/组合/装饰/享元/代理
    public void designmodeStruct(){
        //adapt适配器模式 从一个接口转换为另一个接口 持有组合进一个原有接口实例，然后实现一个接口并在其内融进源接口
        Callable<String> call=new Callable<>(){
            public String call(){
                System.out.println("called  .....");
                return "hhhh";
            }
        };
        Runnable run=new AdaptCall2Run(call);
        run.run();
        //桥接模式：以组合代替继承; 桥接出一个boss品牌和混合动力的汽车对象
        var newCar=new BossCar(new HybridEngine());
        newCar.sayMe();
        //装饰模式： 同一接口下对原始接口实例进行动态化的装饰
        var span=new SpanTextNode();
        span.setText("hello world!");        
        var decorator=new BoldTextNodeDecorator(span);
        System.out.println("源内容："+span.getText()+"  装饰后内容："+decorator.getText());
        //代理模式  提供统一接口功能下的内埋辅助特性模式（远程代理/懒构建/鉴权等）
        var reg =new proxyRegister();
        System.out.println("getName:"+reg.getName()+"  getReg："+reg.reg());
        var reg2 =new proxyRegister22(reg);
        System.out.println("getName:"+reg2.getName()+"  getReg："+reg2.reg());
    }
    //adapt适配器模式 类似电源插头转换器
    /*编写一个Adapter的步骤如下：
        实现目标接口，这里是Runnable；
        内部持有一个待转换接口的引用，这里是通过字段持有Callable接口；
        在目标接口的实现方法内部，调用待转换接口的方法。
    */
    public class AdaptCall2Run implements Runnable{
        private Callable<?> call;
        public AdaptCall2Run(Callable<?> call) {
            this.call=call;
        }
        public void run(){
            try{
                call.call();
            }catch(Exception ex){
                ex.printStackTrace();
            }            
        }
    }
    /* 桥接模式：部件抽象定义，组合引用其具体事项，以代替继承模式，避免多维度子类继承爆炸问题
       桥接是在加工/材料级别的融合模式，与直接可对外提供功能的适配器和代理模式不同！！！
    */
    public interface Engine {
        void start();
    }
    public abstract class Car {        
        protected Engine engine;//抽象部件，可能多种实现的抽象部件
        public Car(Engine engine) {//传入组合进来抽象部件的具体实现，替代继承模式
            this.engine = engine;
        }    
        public abstract void drive();
        public abstract String sayMe();
    }
    public abstract class RefinedCar extends Car {
        public RefinedCar(Engine engine) {
            super(engine);
        }    
        public void drive() {
            this.engine.start();
            System.out.println("Drive " + getBrand() + " car...");
        }    
        public abstract String getBrand();
    }
    public class BossCar extends RefinedCar {
        public BossCar(Engine engine) {
            super(engine);
        }    
        public String getBrand() {
            return "Boss";
        }
        public String sayMe() {
            System.out.println("my is "+getBrand()+ "car ");
            this.engine.start();
            return "Boss";
        }
    }
    public class HybridEngine implements Engine {
        public void start() {
            System.out.println("Start Hybrid Engine...");
        }
    }
    public class ElectricEngine implements Engine {
        public void start() {
            System.out.println("Start Electric Engine...");
        }
    }
    //Composite组合模式 将树层次化的结构一致化抽象化统一化
    public interface Node2 {
        // 添加一个节点为子节点:
        Node2 add(Node2 node);
        // 获取子节点:
        List<Node2> children();
        // 输出为XML:
        String toXml();
    }
    //装饰模式 是在运行期即对象构建完成后，通过组合方式给原有对象增加辅助功能
    //         都是同一抽象接口，功能一致但通过装饰后，附加了辅助功能
    public interface TextNode{
        void setText(String text);
        String getText();
    }
    public class SpanTextNode implements TextNode{//基础实现类
        String text;
        public void setText(String text){
            this.text=text;
        }
        public String getText(){
            return "<span>"+this.text+"</span>";
        }
    }
    public abstract class TextNodeDecorator implements TextNode{//抽象的实现同一接口的装饰类
        TextNode src;
        public TextNodeDecorator(TextNode src){
            this.src=src;
        }
        public void setText(String text){
            this.src.setText(text);
        }        
    } 
    public class BoldTextNodeDecorator extends TextNodeDecorator{//实现具体辅助功能的装饰类        
        public BoldTextNodeDecorator(TextNode src){
            super(src);
        }
        public String getText(){
            return "<b>"+this.src.getText()+"</b>";
        }        
    } 
    //享元模式：即对于不变对象一经创建之后就没有必要再次创建，而可以缓存方式供多次访问使用
    //例如整型布尔类型等不变量的多次获取使用！！
    //享元模式的设计思想是尽量复用已创建的对象，常用于工厂方法内部的优化。
    static public class Student99 {
        // 持有缓存:
        private static final Map<String, Student99> cache = new HashMap<>();    
        // 静态工厂方法:
        public static Student99 create(int id, String name) {
            String key = id + "\n" + name;            
            Student99 std = cache.get(key);// 先查找缓存:
            if (std == null) {// 未找到,创建新对象:                
                System.out.println(String.format("create new Student(%s, %s)", id, name));
                std = new Student99(id, name);                
                cache.put(key, std);// 放入缓存:
            } else {// 缓存中存在:                
                System.out.println(String.format("return cached Student(%s, %s)", std.id, std.name));
            }
            return std;
        }    
        private final int id;
        private final String name;    
        public Student99(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    //代理模式是从A接口转换为A接口，只是内部埋入了辅助功能！
    //代理模式是被调用方（服务方）的主动行为，在调用方（消费者）透明的无感的不用修改代码的使用！！
    interface register {
        String getName();
        String reg();
    }
    static public class proxyRegister implements register{
        register reg;
        public proxyRegister(){            
        }
        private register getReg(){
            if(reg==null){
                reg=new register(){
                   public String getName(){
                       return "new name";
                   }
                   public String reg(){
                       return "reg reg";
                   }
                };
            }
            return reg;
        }
        public String getName(){
            return this.getReg().getName();
        }
        public String reg(){
            if(this.getReg().getName()!="1111"){
                return "no no no non non";
            }else {
                return this.getReg().reg();
            } 
        }
    }
    static public class proxyRegister22 implements register{
        register reg;
        public proxyRegister22(register reg){
            this.reg=reg;
        }
        public String getName(){
            return this.reg.getName();
        }
        public String reg(){
            if(this.reg.getName()!="1111"){
                return "否 否 否 否";
            }else {
                return this.reg.reg();
            } 
        }
    }

    /* 解耦对象的创建和使用
    */     
    public void designModeCreate(){
        //工厂方法，隐藏具体的工厂实现
        var fac=MyFactory.getFactory();
        fac.function1();
        //静态工厂使用方法
        MyFactory.function2();
        //抽象工厂使用方法
        var fac2=MyFactory.getFactory("111");
        var hh=fac2.createHtmlDoc();
        hh.outputHtml();
        var wh=fac2.createWordDoc();
        wh.outputWord();
        //Builder模式是为了创建一个复杂的对象，需要多个步骤完成创建，
        //   或者需要多个零件组装的场景，且创建过程中可以灵活调用不同的步骤或组件。
        String url = URLBuilder.getFactory()
            .builder() // 创建Builder
            .setDomain("www.liaoxuefeng.com") //设置domain 零部件
            .setScheme(true) // 设置scheme 零部件
            .setPath("/") // 设置路径 零部件
            .setQuery(Map.of("a", "123", "q", "K&R")) // 设置query
            .build(); // 完成build组装
        //prototype 原型模式：从现有实例对象构造出新的对象实例
        var std=new Student992();
        std.id=1;
        std.name="小张";
        var newStd=std.copy();//构造出同校同学
        newStd.name="同校的小李";
        var emp=std.copy2();//从学校分配工作到公司员工
        //单例模式保证单个进程内只有一个对象实例，隐藏构造方法；枚举更好！！
        var single1=Singleton.getInstance();
        single1.getName();
        var single2=Singleton22.INSTANCE;
        single2.getName();
    }   
    interface wordhandler{
        String outputWord();
        String formatWord();
    }
    interface htmlhandler{
        String outputHtml();
        String formatHtml();
    }
    interface MyFactory{
        default int function1(){
            return 0;//为兼容演示，设置为缺省方法
        };
        static MyFactory getFactory(){
            return fac;
        }
        static MyFactory fac=new App.MyFactoryImp1();
        //静态工厂方法
        static int function2(){
            return 8888;
        }
        //抽象工厂需要的系列抽象产品
        default wordhandler createWordDoc(){
            return null;//为兼容演示，设置为缺省方法
        }
        default htmlhandler createHtmlDoc(){
            return null;//为兼容演示，设置为缺省方法
        }
        static MyFactory getFactory(String type){
            if(type=="111"){
                return new MyFactoryImp12();
            }else 
                return new MyFactoryImp13();
        }        
    }
    static class MyFactoryImp1 implements MyFactory{
        public int function1(){
            return 6666;
        }
    }
    static class MyFactoryImp12 implements MyFactory{
        public wordhandler createWordDoc(){
            return new wordhandler(){
                public String outputWord(){
                    return "outputWord1111111";
                };
                public String formatWord(){
                    return "outputWord22222222222";
                }
            };
        }
        public htmlhandler createHtmlDoc(){
            return new htmlhandler(){
                public String outputHtml(){
                    return "outputHtml11111111";
                };
                public String formatHtml(){
                    return "outputHtml22222222";
                }
            };
        }
    }
    static class MyFactoryImp13 implements MyFactory{
        public wordhandler createWordDoc(){
            return new wordhandler(){
                public String outputWord(){
                    return "outputWord333333";
                };
                public String formatWord(){
                    return "outputWord444444";
                }
            };
        }
        public htmlhandler createHtmlDoc(){
            return new htmlhandler(){
                public String outputHtml(){
                    return "outputHtm333333";
                };
                public String formatHtml(){
                    return "outputHtml44444";
                }
            };
        }
    }
    interface URLBuilder{
        public final static String SSL_SCHEME = "https://";
        public final static String NO_SSL_SCHEME = "http://";    
        public URLBuilder builder();
        public URLBuilder setDomain(String domain);
        public URLBuilder setScheme(boolean isSSl);
        public URLBuilder setPath(String path);
        public URLBuilder setQuery(Map<String, String> query);
        public String build();
        static URLBuilder getFactory(){
            return new MyURLBuilder();
        }
    }
    static class MyURLBuilder implements URLBuilder{
        private String scheme;
        private String domain;
        private List<String> path;
        private Map<String, String> query;    
        public MyURLBuilder() {
            this.scheme = URLBuilder.SSL_SCHEME;
            this.path = new ArrayList<>();
            this.query = new HashMap<>();
        }    
        @Override
        public URLBuilder builder() {
            return new MyURLBuilder();
        }
        @Override
        public URLBuilder setDomain(String domain) {
            this.domain = domain;
            return this;//方便链式调用
        }    
        @Override
        public URLBuilder setScheme(boolean isSSl) {
            this.scheme = isSSl?URLBuilder.SSL_SCHEME:URLBuilder.NO_SSL_SCHEME;
            return this;
        }    
        @Override
        public URLBuilder setPath(String path) {
            this.path.add(path);
            return this;
        }    
        @Override
        public URLBuilder setQuery(Map<String, String> query) {
            this.query.putAll(query);
            return this;
        }    
        @Override
        public String build() {
            final StringJoiner stringQuery = new StringJoiner("&");
            query.forEach((key, value) -> stringQuery.add(key + "=" + value));
            final StringJoiner stringPath = new StringJoiner("/", scheme, "?"+stringQuery.toString());
            stringPath.add(domain);
            path.forEach(stringPath::add);
            return stringPath.toString();
        }
    }
    //prototype原型模式：从现有对象实例构造出一个新实例
    //会持有类似文件、Socket这样的资源，而这些资源是无法复制给另一个对象共享的，
    // 只有存储简单类型的“值”对象可以复制。
    static class Student992 {
        private int id;
        private String school;
        private String name;
        private int score;    
        public Student992 copy() {
            Student992 std = new Student992();
            std.id = this.id;
            std.school = this.school;
            std.name = this.name;
            return std;
        }
        public Employeer copy2() {//从学生实例构造出一个员工
            Employeer epe = new Employeer();
            epe.id = this.id;
            epe.name = this.name;
            return epe;
        }
    }
    static class Employeer {
        private int id;
        private String fromSchool;
        private String name;          
    }
    //单例模式，保证一个进程中就一个实例对象
    static class Singleton{   
        // private构造方法保证外部无法实例化:     
        private Singleton(){
        }
        private static Singleton obj;
        static public Singleton getInstance111(){
            //遗憾的是，这种写法在多线程中是错误的，在竞争条件下会创建出多个实例。
            //必须对整个方法进行加锁：但加锁会严重影响并发性能。
            if(obj==null)
                obj=new Singleton(); 
            return obj;
        }
        //标准简洁线程安全模式
        private static Singleton obj2=new Singleton();
        static public Singleton getInstance(){            
            return obj2;
        }
        private String name = "world";
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    //基于java内部对枚举看作特殊类，保证枚举中各枚举项唯一的方式构建单例模式
    //序列化和反序列化会绕过普通类的private构造方法从而创建出多个实例，而枚举类就没有这个问题。
    static enum Singleton22{
        INSTANCE;//单个枚举 形成单例
        private String name = "world";
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

    public void steamAboutTest(){
        /* stream 并行流适合计算密集型场景！！！而非i/o密集型；避免过多长时io任务阻塞少量工作线程问题！           
        */
        List<String> stringList = new ArrayList<>();
        List<String> stringList2 = new ArrayList<>();
        for (int i = 0; i < 13; i++) stringList.add(String.valueOf(i));
        for (int i = 91; i < 93; i++) stringList2.add(String.valueOf(i));
        /** 并行流默认采用的是forkjoin 公共静态common池，注意与其他线程之间的干扰，如tomcat容器内多个应用之间的隔离
        */
        stringList.parallelStream().forEach(each -> {
            System.out.println(Thread.currentThread().getName() + " :: " + each);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        /** 可以单独构建一个FJP池，以与其他线程之间的隔离，如tomcat容器内多个应用之间的隔离
        */
        var newFJP=new ForkJoinPool(3);
        newFJP.submit(()->{
            stringList2.parallelStream().forEach(each -> {
                System.out.println(Thread.currentThread().getName() + " :: " + each);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });
        /* 流源构建基于可分割的数据源spliterator流Head pipeline stage
           中间操作返回包装了opWrapSink以及内置sink中包装本身操作逻辑以及与downstream关联的逻辑；
                同时建立向上绑定互链，返回新流便于链式关联后续stage
            终端操作，触发执行流，先反向嵌套包装sink，构建以Head为外包装的sink体；
            从head开始进行串并执行，begin/accept/cancle/end 内置对下游的级联处理
        */
        List.of(1,2,6,9,0).stream()
        .limit(3)
        .sorted((i1,i2)->{
            System.out.println("bbbbbb");
            return 1;
        })        
        /* 终端操作的上下文PipeLine是sorted构建的！！！！
           终端操作本身不创建PipeLine Stage
           但是终端操作会基于当前PipeLine链上下文构建Sink接口实例
                implements TerminalOp<T, Void>, TerminalSink<T, Void>
                interface TerminalSink<T, R> extends Sink<T>, Supplier<R> { }
                                     begin/accept/cancle/end    T get
        */
        .forEach((s)->{
            System.out.println(s);
            try{
                Thread.sleep(1000);
            }catch(Exception ex){
            }
            //通过异常中止拉流
            //throw new RuntimeException("强制停止！");
        });
        /*重点在finally块里，隐式调用了Stream的close方法。Stream的close方法的作用在其注释里面有说明：
            Closes this stream, causing all close handlers for this stream pipeline to be called.
            给Stream添加一个close handler。Stream的close方法调用的close handlers正是通过这个方法添加的。
            Files.lines()内部已内嵌设置了一个closeHandler
        */
        try (Stream<String> lines = Files.lines(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt"))) {
            lines.onClose(()->{
                System.out.println("自定义的closeHandler111111111111");
            })
            .limit(3)
            .onClose(()->{
                System.out.println("自定义的closeHandler22222222");
            })
            .forEach((s)->{
                System.out.println(s);
                try{
                    Thread.sleep(1000);
                }catch(Exception ex){
                }    
                throw new RuntimeException("DDDDDDDD");            
            });
        }catch(Exception ex){
            ex.printStackTrace();
        }
        try{       
            Stream<String> lines = Files.lines(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt"));
            System.out.println("文件已关闭可重新打开");
        }catch(Exception ex){
            ex.printStackTrace();
        } 
    }    
    public void forkjoinTest2(){
        try{
            System.out.println(String.format("  >>> 电脑 CPU 并行处理线程数 :: %s, 并行流公共线程池内线程数 :: %s",
            Runtime.getRuntime().availableProcessors(),
            ForkJoinPool.commonPool().getParallelism()));
            var task1=new MyForkJoinTask("fjt-1111");

            //task1.invoke();//若当前线程为外部线程非forkjoin工作线程，则会在当前线程直接执行此任务
            //task1.fork();//当前线程非forkjoin工作线程情况下，会将此任务打入common静态池执行
            //默认情况下，forkjoin线程为deamon的线程！！！会自动无条件被终止，所以不要放置系统资源！！
            //System.out.println(String.format("%s 当前线程当前线程当前线程：%s ",getNowTime(),Thread.currentThread().getName()));
            //System.out.println(task1.get());            
            
            /* 线程本身异常终止后，会从池中注销其身份信息！！
               工作线程一次扫描执行过程：
                  先执行偷来的任务，然后遍历执行自己队列中的所有任务，
                  单个任务的执行体doExec方法中已经很好的包装了异常体系，所以基本上不用担心一个任务的失败传染造成的整体异常问题；
            */
            var newPool=new ForkJoinPool(3,ForkJoinPool.defaultForkJoinWorkerThreadFactory,
                new Thread.UncaughtExceptionHandler(){
                public void uncaughtException(Thread t, Throwable e){
                    /* 由于ForkJoinWorkerThread的run方法中已经很好的包装了exception，
                        所以几乎不可能出现uncaughtException然后由此handler来处理                        
                    */
                    System.out.println(String.format("%s ForkJoinPool#UncaughtExceptionHandler  异常线程：%s  异常内容：%s "
                    ,getNowTime(),t.getName(),e.toString()));
                }
            },false);
            //newPool.execute(task1);
            //newPool.submit(task1);
            //var res22=newPool.submit(task1).get();//阻塞直接等待结果
            var res33=newPool.invoke(task1);//提交并阻塞等待结果

            System.out.println(String.format("%s 当前线程当前线程当前线程：%s ",getNowTime(),Thread.currentThread().getName()));            
            try{//等待ForkjoinPool中的线程执行完毕，因为默认work线程为deamon模式
                Thread.sleep(8000);
                System.out.println(newPool.toString());
                // task1.isDone();//执行完毕，但不一定正常啊
                // task1.isCompletedAbnormally();//异常结束
                // task1.getException();//获取异常
                // task1.isCompletedNormally();//正常结束
            }catch(Exception ex){
                System.out.println(String.format("%s 异常",getNowTime()));
            }         
            
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    class MyForkJoinTask extends RecursiveTask<Integer>{
        String taskFlag="";
        public MyForkJoinTask(String taskFlag){
            this.taskFlag=taskFlag;
        }
        public Integer compute(){
            System.out.println(String.format("%s 当前线程：%s isDeamon:%s 任务名称：%s",getNowTime(),
                    Thread.currentThread().getName(),Thread.currentThread().isDaemon(), this.taskFlag));
                
                if(this.taskFlag !="fjt-1111"){
                    try{
                        Thread.sleep(6000);
                    }catch(Exception ex){
                        System.out.println(String.format("%s 异常",getNowTime()));
                    }          
                    System.out.println(String.format("%s 执行后 当前线程：%s 任务名称：%s",getNowTime(),
                        Thread.currentThread().getName(), this.taskFlag));
                    return Integer.valueOf(6667);
                }
                var subtask1=new MyForkJoinTask("subtask-6666666");
                subtask1.fork();
                var subtask2=new MyForkJoinTask("subtask-8888888");
                subtask2.fork();
                try{
                    Thread.sleep(5000);
                }catch(Exception ex){
                    System.out.println(String.format("%s 异常",getNowTime()));
                }          
                System.out.println(String.format("%s 执行后11111 当前线程：%s 任务名称：%s",getNowTime(),
                    Thread.currentThread().getName(), this.taskFlag));
                /* join肯定是保证同步化的，虽然内部的工作线程可能在目标任务无法直接完成时，可能去做些其他必要的工作；
                   为避免不必要的等待，因为若join的目标任务在栈顶，则可以直接弹出执行，
                   所以最佳实践方式 st1.fork(); st2.fork(); st2.join();st1.join();方式；
                */
                var s2=subtask2.join();//此处肯定会同步返回  
                var s1=subtask1.join();//虽然期间对应的线程可以在等待目标任务完成时，可以干点别的活
                System.out.println(String.format("%s 执行后22222 当前线程：%s 任务名称：%s",getNowTime(),
                    Thread.currentThread().getName(), this.taskFlag));        
            return Integer.valueOf(100);
        }

    }
    public void funcProgTest(){
        String[] arr=new String[]{ "z","m","c","k","a","p" };
        //实例方法中隐含首参数this因此与  int compare(T o1, T o2); 签名可以一致匹配上
        //        其中的this就是入参s1的实例化引用
        //public static <T> void sort(T[] a, Comparator<? super T> c) 
        Arrays.sort(arr,String::compareTo);
        System.out.println(String.join(";", arr));
        Arrays.sort(arr,(s1,s2)->//不带{}就直接返回表达式结果
            s1.compareToIgnoreCase(s2)
        );
        Arrays.sort(arr,(s1,s2)->{//带{}就必须return
            return s1.compareToIgnoreCase(s2);
        });
        System.out.println(String.join(";", arr));

        List<String> names=List.of("zhang","li","wang","ma");
        //map的入参函数接口方法签名 R apply(T t);
        //Person类的构造方法恰好满足这个条件，因为构造方法的参数是String，而构造方法虽然没有return语句，
        //但它会隐式地返回this实例，类型就是Person，因此，此处可以引用构造方法。
        //构造方法的引用写法是类名::new，因此，此处传入Person::new。
        List<Person991> persons= names.stream().map(Person991::new).collect(Collectors.toList());
        System.out.println(persons);
        /*  符合方法签名的静态方法；
            符合方法签名的实例方法（实例类型被看做第一个参数类型）；
            符合方法签名的构造方法（实例类型被看做返回类型）。
        */
        
        /* Steam流式计算
           java对象流式化，然后形成链式管道，管道环节上布置泛型函数进行转换处理（不立即执行，只存储规则）
                最后输出结果环节触发整个管道的执行！！惰性计算结果！
        */
        List.of("a","b","c").stream()
            .filter(s->!s.equals("a"))
            .map(String::toUpperCase)
            .filter(s->!s.equals("C"))
            .collect(Collectors.toList())
            .forEach((s)->{
               System.out.println(s);
            });
        //System.out.println(aabb);
        /*创建Stream的方法有 ：
            通过指定元素、指定数组、指定Collection创建Stream；
            通过Supplier创建Stream，可以是无限序列；
            通过其他类的相关方法创建。
            基本类型的Stream有IntStream、LongStream和DoubleStream。
        */
        try{
            Stream.generate(()->{
                return random.nextInt();//无限生产
            }).map(String::valueOf)
            //.limit(20) //可以限制输出有限个
            .forEach((s)->{
                System.out.println(s);
                try{
                    Thread.sleep(1000);
                }catch(Exception ex){
                }
                //通过异常中止拉流
                throw new RuntimeException("强制停止！");
            });
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
        //文本文件行流 还有很多流
        try (Stream<String> lines = Files.lines(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt"))) {
            lines.forEach(System.out::println);
        }catch(Exception ex){
        }
        //正则分解字符串流
        Pattern p = Pattern.compile("\\s+");
        Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
        s.forEach(System.out::println);
        //解决泛型无基本类型，用基本类型对应的封装类，有装箱拆箱效率问题
        //内部优化的独立基本类型流方式
        // 将int[]数组变为IntStream:
        IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
        // 将Stream<String>转换为LongStream:
        LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
        IntStream ls2 = List.of("1", "2", "3").stream().mapToInt(Integer::parseInt);
        DoubleStream ls3 = List.of("1", "2", "3").stream().mapToDouble(Double::parseDouble);

        var totalMap = List.of("").stream()
            .map((s1)->{  
                if(!s1.isEmpty())              
                    return Integer.valueOf(s1);
                else return 0;
            }).reduce((am,nm)->{
                var i1=am.intValue();          
                var i2=nm.intValue();
                return i1+i2;
            });
        System.out.println(totalMap);
        var totalMap1 = List.of("1111").stream()
            .map((s1)->{                
                return Integer.valueOf(s1);
            }).reduce((am,nm)->{
                var i1=am.intValue();          
                var i2=nm.intValue();
                return i1+i2;
            });
        System.out.println(totalMap1.get());
        var totalMap2 = List.of("1111","2222","3333").stream()
            .map((s1)->{                
                return Integer.valueOf(s1);
            }).reduce((am,nm)->{
                var i1=am.intValue();          
                var i2=nm.intValue();
                return i1+i2;
            });
        System.out.println(totalMap2);

        var pl=List.of(new Person991("zhang"),new Person991("wang"));
        //如果至少两个，则首次会出现两个； 
        var result1 =pl.stream().reduce((p1,p2)->{//如果至少两个，则首次会出现两个；         
            return new Person991(p1.name+"----"+p2.name);
        });
        System.out.println(result1);

        var pl2=List.of(new Person991("zhang"));
        //如果只有一个则根本不进入reduce汇聚函数体； 
        var result2 = pl2.stream().reduce((p1,p2)->{
            return new Person991(p1.name+"----"+p2.name);
        });
        System.out.println(result2);

        var totalMap3 = List.of("aaa=1111","bbbb=2222","cccc=3333").stream()
            .map((s1)->{
                String[] kv=s1.split("\\=");
                return Map.of(kv[0],kv[1]);
                // T reduce(T identity, BinaryOperator<T> accumulator);
            }).reduce(new HashMap<String,String>(),(am,nm)->{
                am.putAll(nm);                
                return am;
            });
        System.out.println(totalMap3);

        var totalMap4 = List.of("aaa=1111","bbbb=2222","cccc=3333").stream()
            .map((s1)->{
                String[] kv=s1.split("\\=");
                return Map.of(kv[0],kv[1]);
                /*combiner只有在parallelStream的情况下才会起作用，
                    因为parallelStream用的是ForkJoin处理导致第二个参数的accumulator在收集时是并行处理的，
                    所以需要组合器combiner来合并每个accumulator收集的结果。
                */
                // <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
            }).reduce("",(am,nm)->{                             
                return am +"---"+ nm.toString();
            },(a,b)->{//并行多线程场景下，多个reduce子结果的汇聚处理
                return a+";;;"+b;
            });
        System.out.println(totalMap4);

        var totalMap5 = List.of("aaa=1111","bbbb=2222","cccc=3333").stream()
            .collect(Collectors.toList());
        System.out.println(totalMap5);
        var totalMap6 = List.of("aaa=1111","bbbb=2222","cccc=3333").stream()
            .collect(Collectors.toMap(
                s2 -> s2.substring(0, s2.indexOf('=')),
                s2 -> s2.substring(s2.indexOf('=') + 1)));
        System.out.println(totalMap6);
        var totalMap7 = List.of("aaa=1111","bbbb=2222","cccc=3333").stream()
            .toArray(String[]::new);
        System.out.println(String.join(";;;", totalMap7));
        var totalMap8 = List.of("a11=1111","b11=2222","a12=3333","b12=4444").stream()
            .collect(Collectors.groupingBy((s2)->s2.subSequence(0, 1),Collectors.toList()));
        System.out.println(totalMap8);


        var totalMap9 = List.of("a11=1111","b11=2222","a12=3333","b12=4444").stream()
            .distinct() //由于相同的判定逻辑比较复杂，其实完全可以用filter来替代
            .skip(2) //跳过前n个
            .limit(3) //截取前n个
            .sorted() //元素需实现Comparable接口 
            .count();
        System.out.println(totalMap9);

        Stream<String> s1 = List.of("A", "B", "C").stream();
        Stream<String> s2 = List.of("D", "E").stream();
        //若是无限流，也按concat中的次序排序元素，无限流在前，则后续流永远无法被流到了就   
        //连接的各个流的类型要兼容！！！     
        Stream<String> s33 = Stream.concat(s1, s2);//s1 元素在前 s2元素在后
        System.out.println(s33.collect(Collectors.toList())); // [A, B, C, D, E]
        
        // var s4=Stream.generate(()->{
        //     return random.nextInt();//无限生产
        // });
        // var ss5=Stream.concat(List.of(1,2,3,4).stream() ,s4);        
        // ss5.forEach((ii)->{
        //     System.out.println(ii);
        //     try{
        //         Thread.sleep(1000);
        //     }catch(Exception ex){
        //     }
        // });

        Stream<List<Integer>> s6 = Stream.of(
                Arrays.asList(1, 2, 3),
                Arrays.asList(4, 5, 6),
                Arrays.asList(7, 8, 9));
        s6.flatMap((list)->{
            //return list.stream();
            return list.stream().map((iii)->{
                return new Person991("name-"+iii);
            });
        }).forEach(System.out::println);

    }
    public class Person991{
        String name="";
        public Person991(String name){
            this.name=name;
        }
        public String getName() {
            return this.name;
        }
        public String toString(){
            return this.name;
        }
    }
    // JDBC连接的URL, 不同数据库有不同的格式:  注意utf8编码和可以执行多条sql语句带分号模式    
    static String JDBC_URL = "jdbc:mysql://192.168.1.135:3306/TEST?useSSL=false&characterEncoding=utf8&allowMultiQueries=true";
    static String JDBC_USER = "root";
    static String JDBC_PASSWORD = "770619";
    public void jdbcHandler(){            
        try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {
            /* 连接处理模式：即用即开关还是池化复用  线程内复用（避免并发复用造成关键业务事务问题/或者关键业务独立事务则开启独立连接）
               参数化处理机制/事务处理机制/批量执行：mysql开启多语句模式以及使用execute方法
               Statment和ResultSet都是需要关闭的资源，因此嵌套使用try (resource)确保及时关闭；
            */
            try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
                ps.setObject(1, "M"); // 注意：索引从1开始
                ps.setObject(2, 3);
                try (ResultSet rs = ps.executeQuery()) {
                    //rs.next()用于判断是否有下一行记录，
                    //如果有，将自动把当前行移动到下一行（一开始获得ResultSet时当前行不是第一行）；
                    while (rs.next()) {
                        //采用索引方式，则索引从1开始
                        // long id = rs.getLong(1); 
                        // long grade = rs.getLong(2);
                        // String name = rs.getString(3);
                        // int gender = rs.getInt(4);      
                        //能用列名尽量用列名方式              
                        long id = rs.getLong("id");
                        long grade = rs.getLong("grade");
                        String name = rs.getString("name");
                        String gender = rs.getString("gender");
                        System.out.println(String.format("id=%s grade=%s name=%s gender=%s", id,grade,name,gender));
                        /*SQL数据类型	Java数据类型
                            BIT, BOOL	boolean
                            INTEGER	int
                            BIGINT	long
                            REAL	float
                            FLOAT, DOUBLE	double
                            CHAR, VARCHAR	String
                            DECIMAL	BigDecimal
                            DATE	java.sql.Date, LocalDate
                            TIME	java.sql.Time, LocalTime
                        
                        */
                    }
                }
            }
            try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (grade, name, gender) VALUES (?,?,?)",
                    Statement.RETURN_GENERATED_KEYS)) {//返回一个语句事务内的立即自增字段的结果信息
                ps.setObject(1, 1); // grade
                ps.setObject(2, "天龙在家"); // name
                ps.setObject(3, 1); // gender
                //更新操作（INSERT、UPDATE和DELETE）使用executeUpdate()进行，返回受影响的行数。
                int n = ps.executeUpdate(); 
                System.out.println(String.format("insert操作影响  %s 行",n));
                try (ResultSet rs = ps.getGeneratedKeys()) {
                    if (rs.next()) {
                        long id = rs.getLong(1); // 注意：索引从1开始
                        System.out.println(String.format("获取到的自增id = %s",id));
                    }
                }
            }
            try (PreparedStatement ps = conn.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
                ps.setObject(1, "Bob啊啊啊"); // 注意：索引从1开始
                ps.setObject(2, 14);
                int n = ps.executeUpdate(); // 返回更新的行数
                System.out.println(String.format("update操作影响  %s 行",n));
            }
            try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?")) {
                ps.setObject(1, 14); // 注意：索引从1开始
                int n = ps.executeUpdate(); // 删除的行数
                System.out.println(String.format("delete操作影响  %s 行",n));
            }
            try (PreparedStatement ps = conn.prepareStatement("DELETE FROM students WHERE id=?;DELETE FROM students WHERE id=?;")) {
                ps.setObject(1, 5); // 注意：索引从1开始
                ps.setObject(2, 6); 
                var n = ps.executeUpdate(); // 删除的行数
                System.out.println(String.format("批量delete操作影响  %s 行",n));
            }
            /* 把同一个SQL但参数不同的若干次操作合并为一个batch执行 */
            try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
                // 对同一个PreparedStatement反复设置参数并调用addBatch():
                for (var ii=0;ii<6;ii++) {
                    ps.setString(1, "name__"+ii);
                    ps.setBoolean(2, false);
                    ps.setInt(3, 1);
                    ps.setInt(4, ii);
                    ps.addBatch(); // 添加到batch
                }
                // 执行batch:
                int[] ns = ps.executeBatch();
                for (int n : ns) {
                    System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
                }
            }
            
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
        /* 非常重要的独立事务安全起见可以开启一个独立连接，避免复用情况下，造成事务串扰误提交
           因为在一个连接会话被复用的情况下，其他代码逻辑中使用此连接会话：手工进行了commit 或者重新开启了新的事务，
           都会将之前的已成功执行的sql进行commit！！

           on duplicate key update 有很多局限，例如不能使用where查询，会对自增键影响等；
           因此建议upsert采用隔离级别 Serializable 以保证不产生幻读造成重复插入记录
                已测试，upsert端开启Serializable后，可以与其他相关事务进行互锁串行化！！
                切记，要短小精悍的快速执行，以避免锁性能问题！！！
        */        
        try (Connection conn2 = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {  
            try{
                // 设定隔离级别 即对于同一目标数据对象，多个并发事务间的数据隔离级别
                //  并发事务可以在不同连接下的事务！！
                //mysql默认为重复读隔离级别，最佳模式；因为幻读很少出现！！
                conn2.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
                conn2.setAutoCommit(false);// 关闭自动提交:
                // 执行多条SQL语句:
                try (PreparedStatement ps = conn2.prepareStatement("UPDATE students SET name=? WHERE id=?")) {
                    ps.setObject(1, "啊反对法大幅度反对法"); // 注意：索引从1开始
                    ps.setObject(2, 15);
                    int n = ps.executeUpdate(); // 返回更新的行数
                    System.out.println(String.format("update操作影响  %s 行",n));
                }
                try (PreparedStatement ps = conn2.prepareStatement("DELETE FROM students WHERE id22=?")) {
                    ps.setObject(1, 18); // 注意：索引从1开始
                    int n = ps.executeUpdate(); // 删除的行数
                    System.out.println(String.format("delete操作影响  %s 行",n));
                }    
                //最后提交事务，只要之前存在sql异常，都不会执行到此语句！！！
                conn2.commit();
            }catch(Exception ex){               
                conn2.rollback();
                System.out.println(ex.toString());
            }            
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
        /*第一次调用ds.getConnection()，会迫使连接池内部先创建一个Connection，再返回给客户端使用。
          当我们调用conn.close()方法时（在try(resource){...}结束处），不是真正“关闭”连接，而是释放到连接池中，以便下次获取连接时能直接返回。
        */
        try (Connection conn = getJdbcDataSource().getConnection()) { // 在此获取连接
            try (PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?")) {
                ps.setObject(1, "M"); // 注意：索引从1开始
                ps.setObject(2, 3);
                try (ResultSet rs = ps.executeQuery()) {                    
                    while (rs.next()) {                        
                        long id = rs.getLong("id");
                        long grade = rs.getLong("grade");
                        String name = rs.getString("name");
                        String gender = rs.getString("gender");
                        System.out.println(String.format("连接池模式AAA下获取 id=%s grade=%s name=%s gender=%s", id,grade,name,gender));                        
                    }
                }
            }
            
        } // 在此“关闭”连接,不是真正“关闭”连接，而是释放到连接池中
        catch(Exception ex){
            System.out.println(ex.toString());
        }
        try (Connection conn = getJdbcDataSource().getConnection()) { // 在此获取连接
            var sql=new StringBuilder();
            sql.append(String.format("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?;"));
            sql.append(String.format("update students set name='jkjkjkjkjkjkjkjkjkjk' where id=9;"));
            sql.append(String.format("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?;"));
            try (PreparedStatement ps = conn.prepareStatement(sql.toString())) {
                ps.setObject(1, "M"); // 注意：索引从1开始
                ps.setObject(2, 3);
                ps.setObject(3, "M"); // 注意：索引从1开始
                ps.setObject(4, 3);
                var isResetSet=ps.execute();
                var updateCount=ps.getUpdateCount();
                while(!(isResetSet==false && updateCount==-1)){
                    if(isResetSet){
                        try (ResultSet rs = ps.getResultSet()) {
                            while (rs.next()) {                        
                                long id = rs.getLong("id");
                                long grade = rs.getLong("grade");
                                String name = rs.getString("name");
                                String gender = rs.getString("gender");
                                System.out.println(String.format("连接池模式BBBB下获取 id=%s grade=%s name=%s gender=%s", id,grade,name,gender));                        
                            }
                        }
                    }else{
                        System.out.println("更新影响的行数："+updateCount);
                    }
                    isResetSet=ps.getMoreResults();
                    updateCount=ps.getUpdateCount();
                }
            }            
        } // 在此“关闭”连接,不是真正“关闭”连接，而是释放到连接池中
        catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    /* jdbc复用连接池 为了避免频繁地创建和销毁JDBC连接，我们可以通过连接池（Connection Pool）复用已经创建好的连接。
       JDBC连接池有一个标准的接口javax.sql.DataSource，注意这个类位于Java标准库中，但仅仅是接口。要使用JDBC连接池，我们必须选择一个JDBC连接池的实现
    */
    static DataSource JdbcDataSource;
    public  static DataSource getJdbcDataSource() {
        if(JdbcDataSource==null){            
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl(JDBC_URL);
            config.setUsername(JDBC_USER);
            config.setPassword(JDBC_PASSWORD);
            config.addDataSourceProperty("connectionTimeout", "1000"); // 连接超时：1秒
            config.addDataSourceProperty("idleTimeout", "60000"); // 空闲超时：60秒
            config.addDataSourceProperty("maximumPoolSize", "10"); // 最大连接数：10
            JdbcDataSource = new HikariDataSource(config);
        }
        return JdbcDataSource;
    }
    /*Jackson自动检测源中使用的编码：【根据JSON规范，只有有效编码为UTF-8，UTF-16和UTF-32。不能使用其他编码（如Latin-1）】。 
      因此，自动检测很容易并且由解析器完成 - 因此没有接受编码检测。 因此，如果输入为UTF-8，则会被检测到。
      对于输出，UTF-8是默认值;但是，如果您明确要使用其他编码，则可以显式创建JsonGenerator（使用JsonEncoding的方法），并将其传递给ObjectMapper。
      或者在这两种情况下，您当然可以手动构建java.io.Reader / java.io.Writer，并使其使用您想要的任何编码。    
    */
    private static ObjectMapper JSON_MAPPER = new ObjectMapper();
    public void jsonHandler(){
        try{
            //对象转json
            User user = new User("tom",23,"上海");
            String json = JSON_MAPPER.writeValueAsString(user);
            System.out.println(json);
            //json转对象
            User uuser = JSON_MAPPER.readValue(json, User.class);
            System.out.println(uuser);
            
            //map<String,String>转json
            HashMap<String, String> map = new HashMap<String,String>();
            map.put("name", "jack");map.put("city", "beijin");
            String mjson = JSON_MAPPER.writeValueAsString(map);
            System.out.println(mjson);
            //json转map<String,String>
            @SuppressWarnings("unchecked")
            HashMap<String,String> mmap = JSON_MAPPER.readValue(mjson, HashMap.class);
            System.out.println(mmap);

            //非常强大，可以相互转换嵌套的map！！！！
            HashMap<String,HashMap<String,String>> hm=new HashMap<>();
            var subhm=new HashMap<String,String>();
            subhm.put("aa1111", "11111111");
            subhm.put("aa2222", "22222222");
            hm.put("aa",subhm);
            var subhm2=new HashMap<String,String>();
            subhm2.put("bb1111", "676666");
            subhm2.put("bb2222", "888888");
            hm.put("bb",subhm2);
            String mjson2 = JSON_MAPPER.writeValueAsString(hm);
            System.out.println(mjson2);
            //json转map<String,String>
            @SuppressWarnings("unchecked")
            HashMap<String,HashMap<String,String>> mmap2 = JSON_MAPPER.readValue(mjson2, HashMap.class);
            System.out.println(mmap2);
            
            //map<String,User>转json
            HashMap<String, User> umap = new HashMap<String, User>();
            umap.put(user.getName(), user);
            String mmjson = JSON_MAPPER.writeValueAsString(umap);
            System.out.println(mmjson);
            //json转map<String,User>
            JavaType jvt = JSON_MAPPER.getTypeFactory().constructParametricType(HashMap.class,String.class,User.class);
            Map<String,User> urMap = JSON_MAPPER.readValue(mmjson, jvt);
            System.out.println(urMap);
                        
            //list<String>转json
            ArrayList<String> list = new ArrayList<String>();
            list.add("jack");
            list.add("tom");
            String ljson = JSON_MAPPER.writeValueAsString(list);
            System.out.println(ljson);
            //json转list<String>
            @SuppressWarnings("unchecked")
            ArrayList<String> sList = JSON_MAPPER.readValue(ljson, ArrayList.class);
            System.out.println(sList);
            
            //list<User>转json
            List<User> ulist = new ArrayList<User>();
            ulist.add(user);
            String ujson = JSON_MAPPER.writeValueAsString(ulist);
            System.out.println(ujson);
            //json转list<User>
            JavaType jt = JSON_MAPPER.getTypeFactory().constructParametricType(ArrayList.class, User.class);
            List<User> urlist = JSON_MAPPER.readValue(ujson, jt);
            System.out.println(urlist);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }     
    public void xmlTest(){
        //DOM块式对象模式
        try(InputStream input = this.getClass().getResourceAsStream("/book.xml");){
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            org.w3c.dom.Document doc = db.parse(input);     
            this.printNode(doc.getDocumentElement(),4);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
        //SAX流式事件模式
        try(InputStream input = this.getClass().getResourceAsStream("/book.xml");){
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser saxParser = spf.newSAXParser();
            saxParser.parse(input, new MySaxHandler());
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }
    public void printNode(Node n, int indent) {
        for (int i = 0; i < indent; i++) {
            System.out.print(' ');
        }        
        switch (n.getNodeType()) {
            case Node.DOCUMENT_NODE: // Document节点
                System.out.println("Document: " + n.getNodeName());
                break;
            case Node.ELEMENT_NODE: // 元素节点
                System.out.println("Element: " + n.getNodeName());
                break;
            case Node.TEXT_NODE: // 文本
                System.out.println("Text: " + n.getNodeName() + " = " + n.getNodeValue());
                break;
            case Node.ATTRIBUTE_NODE: // 属性
                System.out.println("Attr: " + n.getNodeName() + " = " + n.getNodeValue());
                break;
            default: // 其他
                System.out.println("NodeType: " + n.getNodeType() + ", NodeName: " + n.getNodeName());
        }
        if (n.hasAttributes()) {
			NamedNodeMap as = n.getAttributes();
			for (int i = 0; i < as.getLength(); i++) {
				printNode(as.item(i), indent + 1);
			}
		}
        for (Node child = n.getFirstChild(); child != null; child = child.getNextSibling()) {
            printNode(child, indent + 1);
        }
    }
    class MySaxHandler extends DefaultHandler {
        public void startDocument() throws SAXException {
            print("start document");
        }    
        public void endDocument() throws SAXException {
            print("end document");
        }    
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            print("start element:", localName, qName);
            for (int i = 0; i < attributes.getLength(); i++) {				
                print("atts of element:", localName, qName, attributes.getLocalName(i),attributes.getValue(i));
			}
        }    
        public void endElement(String uri, String localName, String qName) throws SAXException {
            print("end element:", localName, qName);
        }    
        public void characters(char[] ch, int start, int length) throws SAXException {
            print("characters:", new String(ch, start, length));
        }    
        public void error(SAXParseException e) throws SAXException {
            print("error:", e);
        }    
        void print(Object... objs) {
            for (Object obj : objs) {
                System.out.print(obj);
                System.out.print(" ");
            }
            System.out.println();
        }
    }

    public void rmiServerTest(){
        System.out.println("create World clock remote service...");   
        try{
            worldclock worldClock = new worldclocksrv();// 实例化一个WorldClock:
            // 将此服务转换为远程服务接口:  此处的端口设置为0，则会随机指定一个端口号！
            worldclock skeleton = (worldclock)UnicastRemoteObject.exportObject(worldClock, 0);
            // 将RMI服务注册到1099端口:
            Registry registry = LocateRegistry.createRegistry(1099);
            // 注册此服务，服务名为"WorldClock":
            registry.rebind("WorldClock", skeleton);
            /* 整个的机制是注册中心对外提供统一入口服务，然后它提供定位锁定具体服务的功能；
               定位到后，rmi通信就可以直接通过具体服务的端口号进行socket的通信了！
            */
        }catch(Exception ex){
            System.out.println(ex.toString());
        }          
    }
    public void rmiClientTest(){
        try{
            // 连接到服务器localhost，端口1099:
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
            // 查找名称为"WorldClock"的服务并强制转型为WorldClock接口:
            worldclock worldClock = (worldclock) registry.lookup("WorldClock");
            // 正常调用接口方法:
            LocalDateTime now = worldClock.getLocalDateTime("Asia/Shanghai");
            // 打印调用结果:
            System.out.println(now);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }

    //从Java 11开始，引入了新的HttpClient，它使用链式调用的API，能大大简化HTTP的处理。
    //首先需要创建一个全局HttpClient实例，因为HttpClient内部使用线程池优化多个HTTP连接，可以复用：
    static HttpClient httpClient = HttpClient.newBuilder().build();
    public void httpclientTest(){
        try{
            URL url = new URL("http://www.baidu.com");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setUseCaches(false);
            conn.setConnectTimeout(5000); // 请求超时5秒
            conn.setRequestProperty("Accept", "*/*");
            //conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 11; Windows NT 5.1)");
            conn.setRequestProperty("User-Agent", "我就是我");            
            conn.connect();
            if (conn.getResponseCode() != 200) {
                throw new RuntimeException("bad response: " +conn.getResponseMessage());
            }
            Map<String, List<String>> map = conn.getHeaderFields();
            for (String key : map.keySet()) {
                System.out.println(key + ": " + map.get(key));
            }
            if(conn.getContentType().contains("text/html")){                
                try (Reader reader = new InputStreamReader(conn.getInputStream(), "utf-8")) {
                    char[] buffer = new char[1000];
                    int n;
                    while ((n = reader.read(buffer)) != -1) {
                        //System.out.println("read " + n + " chars.");
                        for(var ii=0;ii<n;ii++)
                            System.out.print((char)buffer[ii]);
                    }
                    System.out.println();
                }catch(Exception ex){
                    System.out.println(ex.toString());
                } 
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }    
        try{
            String url = "https://www.baidu.com";
            HttpRequest request = HttpRequest.newBuilder(new URI(url))                
                .header("User-Agent", "Java HttpClient").header("Accept", "*/*")                
                .timeout(Duration.ofSeconds(5))// 设置超时:                
                .version(Version.HTTP_2).build();
            /*如果我们要获取图片这样的二进制内容，只需要把HttpResponse.BodyHandlers.ofString()换成HttpResponse.BodyHandlers.ofByteArray()，
                就可以获得一个HttpResponse<byte[]>对象。如果响应的内容很大，不希望一次性全部加载到内存，
                可以使用HttpResponse.BodyHandlers.ofInputStream()获取一个InputStream流，用buffer逐步流式处理内容；
            */
            /*HttpResponse.BodyHandlers.ofString()
              The body is decoded using the character set specified in the Content-Type response header. 
                If there is no such header, or the character set is not supported, then UTF_8 is used.
            */
            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());            
            Map<String, List<String>> headers = response.headers().map();
            for (String header : headers.keySet()) {
                System.out.println(header + ": " + headers.get(header).get(0));
            }
            System.out.println(response.body().substring(0, 1024) + "...");
            
            String url2 = "http://www.example.com/login";
            String body = "username=bob&password=123456";            
            HttpRequest request2 = HttpRequest.newBuilder(new URI(url2))                
                .header("Accept", "*/*")
                .header("Content-Type", "application/x-www-form-urlencoded")                
                .timeout(Duration.ofSeconds(5))
                .version(Version.HTTP_2)
                 // 使用POST并设置Body:
                .POST(BodyPublishers.ofString(body, StandardCharsets.UTF_8)).build();
            HttpResponse<String> response2 = httpClient.send(request2, HttpResponse.BodyHandlers.ofString());
            String s = response2.body();
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 
    }
    
    /*  Session是个通用对象，可以通过properties配置各种provider包括smtp和pop3，以用于后续的收发功能
        javax.mail.Store：该类实现特定邮件协议（如POP3）上的读、写、监视、查找等操作。通过它的getFolder方法打开一个javax.mail.Folder。
        javax.mail.Folder：该类用于描述邮件的分级组织，如收件箱、草稿箱。它的open方法打开分级组织，close方法关闭分级组织，getMessages方法获得分级组织中的邮件，getNewMessageCount方法获得分级组织中新邮件的数量，getUnreadMessageCount方法获得分级组织中未读邮件的数量
        根据MimeMessage的getFrom和getRecipients方法获得邮件的发件人和收件人地址列表，得到的是InternetAddress数组，根据InternetAddress的getAddress方法获得邮件地址，根据InternetAddress的getPersonal方法获得邮件地址的个人信息。
        MimeMessage的getSubject、getSentDate、getMessageID方法获得邮件的主题、发送日期和邮件的ID（表示邮件）。
        通过MimeMessage的getContent方法获得邮件的内容，如果邮件是MIME邮件，那么得到的是一个Multipart的对象，如果是一共普通的文本邮件，那么得到的是BodyPart对象。Multipart可以包含多个BodyPart，而BodyPart本身又可以是Multipart。BodyPart的MimeType类型为“multipart/*”时，表示它是一个Mutipart。
        但一个BodyPart的disposition属性等于Part.ATTACHMENT或者Part.INLINE常量时，表示它带有附件。通过BodyPart的getInputStream方法可以获得附件的输入流。
        由于pop3协议的缺陷，可以换用imap协议
    */
    public void recieveMailTest(){        
        String host = "pop.qq.com";
        int port = 995;
        String popUsername = "158864428";
        String popPwd = "scftvjbaonyzbhbb";
        Properties props = new Properties();
        props.put("mail.store.protocol", "pop3"); // 协议名称
        props.put("mail.pop3.host", host);// POP3主机名
        props.setProperty("mail.pop3.port", String.valueOf(port)); // 端口号
        props.put("mail.pop3.starttls.enable","false");       
        props.put("mail.pop3.ssl.enable","true");   
        // 连接到Store:        
        Session session = Session.getInstance(props, null);
        session.setDebug(true); // 显示调试信息        
        try(Store store = session.getStore("pop3");){//保证close
            store.connect(popUsername, popPwd);
            try(Folder folder = store.getFolder("INBOX");){//获得收件箱 保证close
                /* Folder.READ_ONLY：只读权限
                * Folder.READ_WRITE：可读可写（可以修改邮件的状态）
                */
                folder.open(Folder.READ_WRITE);	//打开收件箱                
                // 由于POP3协议无法获知邮件的状态,所以getUnreadMessageCount得到的是收件箱的未读邮件总数
                System.out.println("未读邮件数: " + folder.getUnreadMessageCount());                
                // 由于POP3协议无法获知邮件的状态,所以下面得到的结果始终都是为0
                System.out.println("删除邮件数: " + folder.getDeletedMessageCount());
                System.out.println("新邮件: " + folder.getNewMessageCount());                
                // 获得收件箱中的邮件总数
                /* qq邮箱默认设置接收最近30天的邮件！！
                   可在“设置”中配置                
                */
                System.out.println("邮件总数: " + folder.getMessageCount());                
                // 得到收件箱中的所有邮件,并解析                
                Message[] messages = folder.getMessages();
                for (Message message : messages) {                    
                    printMailMessage((MimeMessage) message);
                }
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    void printMailMessage(MimeMessage msg) throws IOException, MessagingException {        
        System.out.println("Subject: " + MimeUtility.decodeText(msg.getSubject()));        
        Address[] froms = msg.getFrom();
        InternetAddress address = (InternetAddress) froms[0];
        String personal = address.getPersonal();
        String from = personal == null ? address.getAddress() : (MimeUtility.decodeText(personal) + " <" + address.getAddress() + ">");
        System.out.println("From: " + from);
        var bodyContent=getMailBody(msg);
    }
    String getMailBody(Part part) throws MessagingException, IOException {
        if (part.isMimeType("text/*")) {// Part是文本: 
            var body=part.getContent().toString();
            System.out.println("body: " + body);
            return body;
        }
        if (part.isMimeType("multipart/*")) {// Part是一个Multipart对象:            
            Multipart multipart = (Multipart) part.getContent();            
            for (int i = 0; i < multipart.getCount(); i++) {// 循环解析每个子Part:
                BodyPart bodyPart = multipart.getBodyPart(i);
                String body = getMailBody(bodyPart);
                if (!body.isEmpty()) {                    
                    return body;
                }
            }
        }
        return "";
    }

    public void sendemailTest(){
        var smtpServer="smtp.qq.com";
        var smtpServerPort="465";
        var smtpUsername="158864428";
        var smtpLoginPwd="scftvjbaonyzbhbb";
        var props=new Properties();
        props.put("mail.smtp.host",smtpServer);
        props.put("mail.smtp.port",smtpServerPort);
        props.put("mail.smtp.auth","true");
        props.put("mail.smtp.starttls.enable","false");       
        props.put("mail.smtp.ssl.enable","true");       
        // 获取Session实例:
        Session session = Session.getInstance(props, new Authenticator() {
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(smtpUsername, smtpLoginPwd);
            }
        });
        // 设置debug模式便于调试:
        session.setDebug(true);     
        
        MimeMessage message = new MimeMessage(session);   
        try{
            message.setFrom(new InternetAddress("158864428@qq.com"));
            message.setRecipient(Message.RecipientType.TO, new InternetAddress("158864428@qq.com"));        
            message.setSubject("Hello", "UTF-8");
            message.setText("Hi Xiaoming...", "UTF-8");
            Transport.send(message);
            
            message.setSubject("Hello 这是html网页内容哦", "UTF-8");
            message.setText("<div style='color:brown;'>hello 啊啊啊啊</div>", "UTF-8","html");
            Transport.send(message);

            Multipart multipart=new MimeMultipart();            
            var body=new MimeBodyPart();
            body.setContent("这是正文<p><img src=\"cid:img01\"></p>","text/html;charset=utf-8");
            multipart.addBodyPart(body);
            try(InputStream input = getClass().getResourceAsStream("/test.png");){
                var imgpart=new MimeBodyPart();
                imgpart.setFileName("这就是附件名啊.png");                
                imgpart.setHeader("Content-ID", "<img01>");
                imgpart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/png")));
                multipart.addBodyPart(imgpart);               
            }          
            try(InputStream input = getClass().getResourceAsStream("/test.png");){
                var imgpart=new MimeBodyPart();
                imgpart.setFileName("附件2222.png");                
                imgpart.setHeader("Content-ID", "<img02>");
                imgpart.setDataHandler(new DataHandler(new ByteArrayDataSource(input, "image/png")));
                multipart.addBodyPart(imgpart);               
            }              
            message.setSubject("这是带附件且内嵌链接的邮件", "UTF-8");
            message.setContent(multipart);
            Transport.send(message);

        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    /* UDP无连接相关处理机制，只定向发送不管连接不管对方是否可收到！！不可靠！！
    */
    public void udpserverHandle1(){
        //也是向操作系统申请绑定一个udp端口，操作系统监听接收到一个此目标的报文后就给自己发消息
        try(var ssk = new DatagramSocket(9999,InetAddress.getByName("127.0.0.1"));){
            for(;;){
                var buf=new byte[1024];//此处必须是足够装满目标报文的缓存区，只大不小，否则会截断和乱码
                /*The datagram packet also contains the sender's IP address, and the port number on the sender's machine.
                    If the message is longer than the packet's length, the message is truncated （截断）.
                    因此接收的缓存长度一定要大于报文的单位长度！！！
                */
                var dp=new DatagramPacket(buf, buf.length);
                ssk.receive(dp);
                var s=new String(dp.getData(),dp.getOffset(),dp.getLength(),StandardCharsets.UTF_8);
                System.out.println(String.format("%s 服务端接收到来自%s 的内容： %s",getNowTime(), dp.getSocketAddress(),s));
                /* 数据报文对象中 接收到的报文信息中的发送方ip+port信息与将要发送的对端ip+port信息是存储在同一数据对象中！！
                   因此可以复用！！
                */
                //当服务器收到一个DatagramPacket后，通常必须立刻回复一个或多个UDP包，因为客户端地址在DatagramPacket中，
                //   每次收到的DatagramPacket可能是不同的客户端，如果不回复，客户端就收不到任何UDP包。
                dp.setData("ACK啊啊啊".getBytes("utf-8"));
                ssk.send(dp);
            }            
        }catch(Throwable ex){
            MyLogger.error("udpServer 发生异常"+ex.toString());
        }
    }
    public void udpClientHandle1(){
        try(var csk = new DatagramSocket();){
            csk.setSoTimeout(1000);//也可以设置读取超时
            //UDP是不面向连接的服务，即发送接收前双方不用先建立连接；
            //因此此处连接非双端连接，而只是指定数据报文中的目标服务器和端口
            csk.connect(InetAddress.getByName("127.0.0.1"), 9999);
            byte[] data="中国人民万岁".getBytes("utf-8");
            var dp=new DatagramPacket(data, data.length);
            csk.send(dp);
            byte[] buf=new byte[1024];//大小必须足够放下目标内容，否则会被截断
            var packet=new DatagramPacket(buf, buf.length);
            csk.receive(packet);//在已被操作系统随机分配的端口上，接收对端发来的报文
            var s=new String(packet.getData(),packet.getOffset(),packet.getLength(),StandardCharsets.UTF_8);
            System.out.println(String.format("%s 客户端接收到来自%s 的内容： %s",getNowTime(), packet.getSocketAddress(),s));
            csk.disconnect();//清空指定服务器和端口
        }catch(Throwable ex){
            MyLogger.error("udpClient 发生异常"+ex.toString());
        }
    }

    /* TCP底层时有连接握手处理机制以及连接保活以及连接close以及重传等可靠传输机制！
    */
    public void tcpServerHandle1(){        
        //对应着向操作系统申请绑定bind某个ip+端口，来了这样目标的tcp连接请求就给自己中断发消息处理，带backlog队列
        try(var ssk = new ServerSocket(6666,5,InetAddress.getByName("127.0.0.1"))){
            for(;;){
                System.out.println(String.format("%s 服务头111111",getNowTime()));
                var socket=ssk.accept();//阻塞模式接收新的连接，并返回一个封装了此连接的socket
                System.out.println(String.format("%s 收到一个socket连接  %s",getNowTime(),socket.getRemoteSocketAddress()));
                var sst=new socketServerTherad(socket);
                sst.start();
            }
        }catch(Throwable ex){           
            MyLogger.error("tcpServer 发生异常"+ex.toString());
        }
    }
    class socketServerTherad extends Thread{
        Socket sk;
        public socketServerTherad(Socket sk){
            this.sk=sk;
        }
        @Override
        public void run(){
            try{
                System.out.println(String.format("%s 开始处理socket",getNowTime(),this.sk.getRemoteSocketAddress()));
                try(var inputReader =new BufferedReader(new InputStreamReader(this.sk.getInputStream(),StandardCharsets.UTF_8));
                    var outputWriter =new BufferedWriter(new OutputStreamWriter(this.sk.getOutputStream(),StandardCharsets.UTF_8));){
                    this.sk.setSoTimeout(19000);//设置read()超时
                    outputWriter.write("welcome to you!!!\n");
                    outputWriter.flush();
                    for(;;){
                        System.out.println(String.format("%s 开始接收",getNowTime()));
                        var s=inputReader.readLine();
                        System.out.println(String.format("%s 接收到了",getNowTime()));
                        if(s.equals("bye")){
                            //由客户端主动发起close吧，【失联后reset】服务器端会自动close 
                            //因为tcp底层有连接保活机制，通信失活超时后会自动触发异常的！！！
                            outputWriter.write("bye\n");
                            outputWriter.flush();
                            break;
                        }
                        //Thread.sleep(9000);//给客户端模拟读取超时
                        outputWriter.write("echo " + s +" \n");
                        outputWriter.flush();
                    }
                }
            }catch(Throwable ex){
                System.out.println("服务端服务socket时发生异常："+ex.toString());
                try{
                    this.sk.close();
                }catch(Exception ex2){
                    System.out.println("发生异常后，关闭单个socket连接时又发生异常："+ex2.toString());
                }                
            }
        }
    }
    public void tcpClientHandle1(){
        try(var csk=new Socket()){// try(var csk=new Socket("127.0.0.1",6666)){ 
            SocketAddress socketAddress = new InetSocketAddress("127.0.0.1",6666);
            csk.connect(socketAddress, 60000);//带超时模式
            csk.setSoTimeout(5000);//设置read()超时
            try(var inputReader =new BufferedReader(new InputStreamReader(csk.getInputStream(),StandardCharsets.UTF_8));
                var outputWriter =new BufferedWriter(new OutputStreamWriter(csk.getOutputStream(),StandardCharsets.UTF_8));
                Scanner sc =new Scanner(System.in);){
                //首次获取服务器端的回应
                System.out.println(String.format("%s 开始首次接收",getNowTime()));
                System.out.println(String.format("%s 首次接收到了服务回应：%s",getNowTime(), inputReader.readLine()));                
                for(;;){
                    System.out.println(String.format("%s >>>>>>",getNowTime()));
                    var send=sc.nextLine();
                    outputWriter.write(send);
                    outputWriter.newLine();
                    outputWriter.flush();
                    var s=inputReader.readLine();
                    if(s.equals("bye")){           
                        System.out.println(String.format("%s 收到服务端bye ",getNowTime()));                                                             
                        System.out.println(String.format("%s 客户端主动关闭连接",getNowTime()));
                        break;
                    }
                    System.out.println(String.format("%s >>>>> %s ",getNowTime(),s));
                }
            }catch(Exception ex){
                System.out.println("客户端socket时发生异常："+ex.toString());
                try{
                    csk.close();
                }catch(Exception ex2){
                    System.out.println("发生异常后，关闭socket连接时又发生异常："+ex2.toString());
                }
            }
        //此处内嵌自动 csk.close();/* 通信以客户端为主动方，服务端为被动方； 客户端关闭后，根据tcp协议，服务端也会被关闭，或超时关闭 */
        }catch(Throwable ex){
            MyLogger.error("tcpClient 发生异常"+ex.toString());
        }
    }
    
    /* Runnable  void run()
       Callable  R call()        
       Supply    R supply()      只供应不需要配给
       Accept    void accept(T)  只吃不吐
       Apply     R apply(T)      连吃带吐    

       CompletableFuture 通过在一个独立线程中启动执行一个runnable或supply，
            然后返回一个futrue关联了此独立线程；该独立线程可动态更新并提供该线程的各种状态信息
            然后这个future提供了对绑定的独立线程进行状态以及相关信息的获取功能
            执行的功能，可以在当前线程同步执行，也可以开启一个独立线程进行处理
       切记CompletableFuture使用的线程竟然默认都是守护线程，所以主线程必须join，
            否则主线程退出后，则CompletableFuture线程也直接销毁了！！！
    */
    public void completableFutureTest2(){        
        var future1 = CompletableFuture.supplyAsync(()->{
            System.out.println(String.format("supplyAsync 所在的当前线程：%s  isDeamon: %s"
                , Thread.currentThread().getName(),Thread.currentThread().isDaemon()));
            try{
                Thread.sleep(3000);
            }catch(Exception ex){}
            return String.format("supplyAsync 结果：%s ", "hello hello88888888888");
        });
        future1.thenAccept((result)->{//非异步则执行所在线程为supplyAsync的所在线程
           System.out.println(String.format("thenAccept 所在的当前线程：%s isDeamon: %s 得到的结果信息：%s"
            , Thread.currentThread().getName(),Thread.currentThread().isDaemon(),result));
        });
        future1.thenAcceptAsync((result)->{
            System.out.println(String.format("thenAcceptAsync 所在的当前线程：%s isDeamon: %s  得到的结果信息：%s"
            , Thread.currentThread().getName(),Thread.currentThread().isDaemon(),result));
        });
        System.out.println("主线程执行点 33333");
        try{
            // 主线程不要立刻结束，否则CompletableFuture默认使用的线程池会立刻关闭:
            Thread.sleep(4100);
        }catch(InterruptedException ex){
        }       
    }   
    public void futureMockTest(){
        var future = supplyAsynMock(()->{
            System.out.println("我正在运行111111111111");
            try{
                Thread.sleep(3000);
            }catch(Exception ex){
            }
            return "hello world 模拟出的future真香啊！！";
        });
        var result=future.get(100);        
        System.out.println(result);
        var result1=future.get();        
        System.out.println(result1);
    }
    public <T,R> MyFuture<T,R> supplyAsynMock(Callable<R> ca){
       MyThread3<T,R> thread=new MyThread3<>(ca);
       thread.start();
       return new MyFuture<T,R>(thread);
    }
    class MyFuture<T,R>{        
        private MyThread3<T,R> thread;
        public MyFuture(MyThread3<T,R> _thread){
            this.thread=_thread;
        }
        public boolean isDone(){
            if(thread==null) throw new RuntimeException("无线程对象初始化");
            return thread.isDone();
        }
        public void cancle(){
            if(thread==null) throw new RuntimeException("无线程对象初始化");
            thread.interrupt();
        }
        public R get(final long millis){
            if(thread==null) throw new RuntimeException("无线程对象初始化");
            if(thread.isDone()){
                return thread.getResult();
            }else{
                try{
                    if(millis>0){
                        thread.join(millis);
                    }else{
                        thread.join();
                    }                    
                }catch(Exception ex){
                }                
                return thread.getResult();
            }
        }
        public R get(){
            if(thread==null) throw new RuntimeException("无线程对象初始化");
            if(thread.isDone()){
                return thread.getResult();
            }else{
                try{
                    thread.join();
                }catch(Exception ex){
                }                
                return thread.getResult();
            }
        }
    }
    class MyThread3<T,R> extends Thread{
        public MyThread3(){
            super();
        }        
        Callable<R> ca;
        public MyThread3(Callable<R> ca){
            super();
            this.ca=ca;
        }
        Throwable e;
        public Throwable getError(){
            return e;
        }
        R result;
        public R getResult(){
            return result;
        }
        boolean _isDone=false;
        public boolean isDone(){
            return _isDone;
        }
        @Override
        public void run(){
            try{
                if (this.ca != null) {
                    result = ca.call();
                }
            }catch(Throwable ex){
                System.out.println("发生了异常："+ex.toString());
                this.e=ex;
            }finally{
                _isDone=true;
                System.out.println("内部执行完毕");
            }          
        }
    }
    /* */
    public void forkjoinTest(){
        // 创建2000个随机数组成的数组:
        long[] array = new long[2000];
        long expectedSum = 0;
        for (int i = 0; i < array.length; i++) {
            array[i] = random();
            expectedSum += array[i];
        }
        System.out.println("Expected sum: " + expectedSum);
        // fork/join:
        long forkjoinSum = forkjoinmock(array,0,array.length);
        System.out.println("fork/join sum: " + forkjoinSum);
    }
    public long forkjoinmock(long[] array, int start, int end){
        if((end-start)<200){
            long sum=0;
            for(var index=start;index<end;index++){
                sum+=array[index];
            }
            return sum;
        }else{
            var mid=(end-start)/2;
            var start1=start;
            var end1=start+mid;
            var start2=end1;
            var end2=end;            
            MyThread2<String,Long> t1=new MyThread2<>(()->{
                long forkjoinSum = forkjoinmock(array,start1,end1);                
                return Long.valueOf(forkjoinSum);
            });
            MyThread2<String,Long> t2=new MyThread2<>(()->{
                long forkjoinSum = forkjoinmock(array,start2,end2);                
                return Long.valueOf(forkjoinSum);
            });
            t1.start();
            t2.start();
            try{
                t1.join();
                t2.join();
            }catch(Exception ex){
            }            
            var res1=t1.result;
            var res2=t2.result;
            return res1+res2;
        }
    }
    class MyThread2<T,R> extends Thread{
        public MyThread2(){
            super();
        }
        T inputArg;
        public MyThread2(T arg){
            super();
            this.inputArg=arg;
        }
        Callable<R> ca;
        public MyThread2(Callable<R> ca){
            super();
            this.ca=ca;
        }
        Throwable e;
        R result;
        @Override
        public void run(){
            try{
                if (this.ca != null) {
                    result = ca.call();
                }
            }catch(Throwable ex){
                this.e=ex;
            }            
        }
    }

    //静态全局ThreadLocal以便贯穿不同方法内直接获取，但不影响线程之间的存储隔离
    static ThreadLocal<WebContextForThread> tlContextWeb=new ThreadLocal<>();
    static ThreadLocal<UserContextForThread> tlContextUser=new ThreadLocal<>();
    public void threadLocalTest(){
        var tl=new ThreadLocal<String>();
        tl.set("父线程的值");
        var t1=new Thread(()->{
            tl.set("子线程的值");
            System.out.println("子线程当前的值："+tl.get());
        });
        t1.start();        
        try{
            t1.join();
        }catch(InterruptedException ex){
        }        
        System.out.println("父线程当前的值："+tl.get());

        var tl2=new InheritableThreadLocal<String>();
        tl2.set("父线程的值111111");
        var t2=new Thread(()->{
            //可以被一次性初始化，也可以重新set新值  tl2.set("子线程重新覆盖父线程的新值jkjkjk");         
            System.out.println("子线程当前的值："+tl2.get());
        });
        t2.start();   
        tl2.set("子线程启动后，父线程设置新的值aaaaaaaaaa");     
        try{
            t2.join();
        }catch(InterruptedException ex){
        }        
        System.out.println("父线程当前的值333333："+tl2.get());
        
        var tp=Executors.newSingleThreadExecutor();
        tp.submit(()->{
            //用一个全局上下文对象，避免对threadlocal处理的零零散散            
            try{          
                final var ctxWeb=new WebContextForThread();    
                tlContextWeb.set(ctxWeb);
                final var ctxUser=new UserContextForThread();    
                tlContextUser.set(ctxUser);
                //fun1(); //内部 var ctxWeb = tlContextWeb.get();                
                //fun2(); //内部 var ctxUser = tlContextUser.get();     
            }finally{//释放本线程域内的ThreadLocal以便线程复用场景下，下次线程执行上下文的clean
                tlContextWeb.remove();
                tlContextUser.remove();
            }
            //ctx作用域底部，也是强引用底部
            //出去后，则对应的threadlocal->threadlocalmap->entry->key就会被下次gc回收
            //但线程对象在线程池中一直驻留，所以其下私有成员变量 还会一直强引用 
            //    threadlocal->threadlocalmap->entry->value
            //    entry中的value还是强引用着目标对象，GC不会释放目标对象
            //因此就出现了entry key=null/value=obj的暂时情况            
        });
    }
    class WebContextForThread{

    }
    class UserContextForThread{

    }
    /* 每个线程都有一个独立的存储区ThreadLocal独立属于每个线程，不会互相干扰！
       由此可以建立独立线程全周期的上下文信息，以供线程周期中各个方法安全的无并发的共享使用！避免全局参数的繁重传递！
       同时要注意，线程可能作为线程池中的可复用的线程，因此一个任务生命周期服务完成后，要及时销毁上个周期生成的上下文信息；
        因为当前线程执行完相关代码后，很可能会被重新放入线程池中，如果ThreadLocal没有被清除，该线程执行其他代码时，会把上一次的状态带进去。
    */
    public void threadHandle97(){
        // try (var ctx = new UserContext("Bob")) {
        //     UserContext.set(user);
        //     // 可任意调用UserContext.currentUser():
        //     String currentUser = UserContext.currentUser();                
        //     step1();
        //     step2();             
        // } catch(Throwable trb) {
        //     //处理异常
        // }// 在此自动调用UserContext.close()方法释放ThreadLocal关联对象
    }    
    //定义一个可语法糖自动remove的线程局部变量类
    //UserContext中完全封装了ThreadLocal，
    //外部代码在try (resource) {...}内部可以随时调用UserContext.currentUser()获取当前线程绑定的用户名。
    // public class UserContext implements AutoCloseable {
    //     static final ThreadLocal<String> ctx = new ThreadLocal<>();    
    //     public UserContext(String user) {
    //         ctx.set(user);
    //     }    
    //     public static String currentUser() {
    //         return ctx.get();
    //     }    
    //     @Override
    //     public void close() {
    //         ctx.remove();
    //     }
    // }

    /*Fork/Join 分解后并行执行然后合并汇总； 类似map/reduce
    */
    public void threadHandle96(){
        // 创建2000个随机数组成的数组:
        long[] array = new long[2000];
        long expectedSum = 0;
        for (int i = 0; i < array.length; i++) {
            array[i] = random();
            expectedSum += array[i];
        }
        System.out.println("Expected sum: " + expectedSum);
        // fork/join:
        ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
        long startTime = System.currentTimeMillis();
        Long result = ForkJoinPool.commonPool().invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
    }
    static Random random = new Random(0);
    static long random() {
        return random.nextInt(10000);
    }
    class SumTask extends RecursiveTask<Long> {
        static final int THRESHOLD = 500;
        long[] array;
        int start;
        int end;    
        SumTask(long[] array, int start, int end) {
            this.array = array;
            this.start = start;
            this.end = end;
        }    
        @Override
        protected Long compute() {
            if (end - start <= THRESHOLD) {
                // 如果任务足够小,直接计算:
                long sum = 0;
                for (int i = start; i < end; i++) {
                    sum += this.array[i];
                    // 故意放慢计算速度:
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                    }
                }
                return sum;
            }
            // 任务太大,一分为二:
            int middle = (end + start) / 2;
            System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
            SumTask subtask1 = new SumTask(this.array, start, middle);
            SumTask subtask2 = new SumTask(this.array, middle, end);
            // invokeAll会并行运行两个子任务:
            invokeAll(subtask1, subtask2);
            // 获得子任务的结果: join是同步化的获取
            Long subresult1 = subtask1.join();
            Long subresult2 = subtask2.join();
            Long result = subresult1 + subresult2;
            System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
            return result;
        }
    }

    /* 或anyOf/与allOf模式的组合  
        anyOf()可以实现“任意个CompletableFuture只要一个成功”，
        allOf()可以实现“所有CompletableFuture都必须成功”，这些组合操作可以实现非常复杂的异步流程控制。 
      最后我们注意CompletableFuture的命名规则：
        xxx()：表示该方法将继续在已有的线程中执行，延续之前的线程内执行；
        xxxAsync()：表示将异步在线程池中独立的一个线程中执行。     
    */
    public void threadHandle95(){
        var cf1=CompletableFuture.supplyAsync(()->{
            try{                
                Thread.sleep(2000);
            }catch(InterruptedException ex){
            }    
            System.out.println("cf1");
            return "cf1";
        });
        var cf2=CompletableFuture.supplyAsync(()->{
            System.out.println("cf2");
            return "cf2";
        });
        var cf3=CompletableFuture.anyOf(cf1,cf2);
        var cf4=cf3.thenApplyAsync((result)->{
            try{                
                Thread.sleep(3000);
            }catch(InterruptedException ex){
            }    
            System.out.println("cf3->cf4");
            return result+"--cf4";
        });
        var cf5=cf3.thenApplyAsync((result)->{
            System.out.println("cf3->cf5");
            return result+"--cf5";
        });
        var cf6=CompletableFuture.allOf(cf4,cf5);
        cf6.join();//等待所有的线程完成
        System.out.println("应该都完成了吧");        
    }
    /*串行同步执行串
    */
    public void threadHandle94(){
        CompletableFuture<Integer> cf1=CompletableFuture.supplyAsync(()->{
            try{                
                Thread.sleep(1000);
            }catch(InterruptedException ex){
            }    
            System.out.println("第一个future");
            return Integer.valueOf(11111);
        });        
        //cf1.join() //join的意思是这是个 子线程
        CompletableFuture<String> cf2= cf1.thenApplyAsync((s)->{
            System.out.println("第二个future");
            return String.valueOf(s)+"---22222222";
        });
        cf2.thenAcceptAsync((result)->{
            System.out.println("最终结果："+result);
        });

        System.out.println("主线程执行点 33333");
        try{
            // 主线程不要立刻结束，否则CompletableFuture默认使用的线程池会立刻关闭:
            Thread.sleep(4100);
        }catch(InterruptedException ex){
        }        
    }
    /*furter 是通过对Task的包装并借用wait/notify机制实现同步/回调功能以及在线程执行周期对furter对象进行同步或回调等方法实现    
    */
    public void threadHandle93(){
        var cf=CompletableFuture.supplyAsync(new Supplier<String>(){
            public String get(){
                System.out.println("子线程内00000000000000000");
                //throw new RuntimeException("内部异常7777777777");
                try{
                    Thread.sleep(5000);
                }catch(InterruptedException ex){
                }       
                return "8888888888888888";
            }
        });
        try{
            Thread.sleep(1000);
        }catch(InterruptedException ex){
        }       
        System.out.println("主线程执行点 11111");
        /*如果已经执行完毕则执行lamda回调方法
          如果还没有执行完毕，则从线程池中请求一个线程，等待目标线程的执行完毕！
                                   可通过锁来同步对目标线程执行状态的存取！！
        */
        cf.thenAccept((result)->{
            System.out.println(" AcceptAsync结果1111111:"+result);
        }).thenAccept((result)->{
            System.out.println(" AcceptAsync结果2222222:"+result);
        });
        System.out.println("主线程执行点 222222");
        //在之前开辟的线程上下文中执行回调
        cf.exceptionally((e)->{
            System.out.println(" AcceptAsync异常:"+e.toString());
            return null;
        });
        //在独立线程中执行回调
        cf.exceptionallyAsync((e)->{
            System.out.println(" AcceptAsync异常:"+e.toString());
            return null;
        });
        System.out.println("主线程执行点 33333");
        try{
            // 主线程不要立刻结束，否则CompletableFuture默认使用的线程池会立刻关闭:
            Thread.sleep(4100);
        }catch(InterruptedException ex){
        }        
    }

    /* 通过线程池封装带回参的callable */
    public void threadHandle92(){
        var ec=Executors.newSingleThreadExecutor();
        var furter=ec.submit(new Callable<String>(){
            public String call() throws InterruptedException{  
                Thread.sleep(6000);              
                return "hellloooloooo";
            }
        });
        try{
            System.out.println(furter.get(1,TimeUnit.SECONDS));
        }catch(TimeoutException ex3){            
            System.out.println(" furter.get等待超时： "+ ex3.toString());
        }catch(InterruptedException ex1){
            //当前主线程被发送了中断信号后触发，因为此时主线程阻塞等待结果中
            System.out.println(" furter.get阻塞等待时，线程被发送了中断信号信息： "+ex1.toString());
        }catch(ExecutionException ex2){
            //子线程内发生了内部未捕获的异常
            System.out.println(" furter.get捕获到了子线程内部异常： "+ ex2.toString());
        }

        try{
            if(false){
               System.out.println("发起对子线程的中止命令，结果是：" + furter.cancel(true));
            }else{
                while(!furter.isDone()){
                    System.out.println("等待furter done");
                    Thread.sleep(500);
                }
                System.out.println("子线程执行后的结果："+furter.get());
            }            
        }catch(InterruptedException ex1){
            //当前主线程被发送了中断信号后触发，因为此时主线程阻塞等待结果中
            System.out.println(" furter.get阻塞等待时，线程被发送了中断信号信息： "+ex1.toString());
        }catch(ExecutionException ex2){
            //子线程内发生了内部未捕获的异常
            System.out.println(" furter.get捕获到了子线程内部异常： "+ ex2.toString());
        }   

        ec.shutdown();
    }

    /* Thread只能吃进Runnable，而且Runnable不能throw checked exception
       Callable需要通过Executor线程池封装       
       子线程中发生了异常，如果没有任何类来接手处理的话，是会直接退出的，而不会记录任何日志。
        所以，如果什么都不做的话，是会出现子线程任务既没执行成功，也没有任何日志提示的“诡异”现象的。
       子线程异常处理方法：
       1、在子线程内部进行try catch内部消化处理
       2、通过设置当前线程/当前线程所在线程组或者全局进程级别上的UncaughtExceptionHandler来捕获
          Thread.setUncaughtExceptionHandler 
          ThreadGroup.setUncaughtExceptionHandler 
          Thread.setDefaultUncaughtExceptionHandler
       3、通过线程池扔callable，然后get时trycatch子线程内部异常
    */
    public void threadHandle91(){
        var t1=new Thread(new Runnable(){
            public void run(){
             throw new RuntimeException("111111111111111");
            }
        });
        t1.setName("1111");
        t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(String.format("当前线程设置的缺省异常处理器 线程：%s 异常：%s",t.getName(),e.toString()));
            }
        });
        t1.start();

        var t2=new Thread(new Runnable(){
            public void run(){
             throw new RuntimeException("22222222222222");
            }
        });
        t2.setName("2222");
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
            public void uncaughtException(Thread t, Throwable e) {
                System.out.println(String.format("进程内缺省的异常处理器  线程：%s 异常：%s",t.getName(),e.toString()));
            }
        });
        t2.start();     
        
        var ec=Executors.newSingleThreadExecutor();
        var furter=ec.submit(new Callable<String>(){
            public String call(){
                throw new RuntimeException("3333333333333333333333");
                //return "";
            }
        });
        try{
            var res=furter.get();            
        }catch(InterruptedException ex1){
            //当前主线程被发送了中断信号后触发，因为此时主线程阻塞等待结果中
            System.out.println(" furter.get阻塞等待时，线程被发送了中断信号信息： "+ex1.toString());
        }catch(ExecutionException ex2){
            //子线程内发生了内部未捕获的异常
            System.out.println(" furter.get捕获到了子线程内部异常： "+ ex2.toString());
        }   
        ec.shutdown();
    }

    /*线程池记得捕获异常并shutdown
    */
    public void threadHandle9(){
        var executor=Executors.newFixedThreadPool(3);
        for(var ii=0;ii<10;ii++){
            executor.submit(new Runnable(){
                public void run(){
                     System.out.println(String.format("%s 固定线程池下线程：%s",getNowTime(), Thread.currentThread().getName()));
                } 
             });
        }
        executor.shutdown();      

        //executor=Executors.newCachedThreadPool();
        int min = 4;
        int max = 8;
        ThreadPoolExecutor executor1= new ThreadPoolExecutor(min, max,
                 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        try{
            for(var ii=0;ii<10;ii++){
                if(executor1.getActiveCount()<10){
                    executor1.submit(new Runnable(){
                        public void run(){
                             System.out.println(String.format("%s 动态线程池线程：%s",getNowTime(), Thread.currentThread().getName()));
                        } 
                     });
                }            
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }finally{
            executor1.shutdown();      
        }       
                 
        var se=Executors.newScheduledThreadPool(2);
        // 1秒后执行一次性任务:
        se.schedule(new Runnable(){
            public void run(){
                System.out.println(String.format("%s 1秒后执行一次性任务 线程：%s",getNowTime(), Thread.currentThread().getName()));
            }
        },1, TimeUnit.SECONDS);
        //以某种固定的速率执行
        //se.scheduleAtFixedRate(command, initialDelay, period, unit)
        //执行一个后延迟再执行另一个
        //se.scheduleWithFixedDelay(command, initialDelay, delay, unit)
        se.shutdown();
    }

    /*Java的java.util.concurrent包除了提供底层锁、并发集合外，还提供了一组原子操作的封装类，它们位于java.util.concurrent.atomic包。
        简单说一下原子操作的概念，“原子”代表最小的单位，所以原子操作可以看做最小的执行单位，该操作在执行完毕前不会被任何其他任务或事件打断。
        CAS（CompareAndSet 比较后再设置 由底层系统调用指令封装 是lock-free无锁模式的线程安全机制）相比Synchronized，避免了锁的使用，总体性能比Synchronized高很多。
        public int incrementAndGet(AtomicInteger var) {
            int prev, next;
            do {
                prev = var.get();
                next = prev + 1;
            } while ( ! var.compareAndSet(prev, next));
            return next;
        }
        CAS是指，在这个操作中，如果AtomicInteger的当前值是prev，那么就更新为next，返回true。
        如果AtomicInteger的当前值不是prev，就什么也不干，返回false。
        通过CAS操作并配合do ... while循环，即使其他线程修改了AtomicInteger的值，最终的结果也是正确的。    

        原子操作实现了无锁的线程安全；
        适用于计数器，累加器等。
    */
    public void threadHandle8(){
        for(var i=0;i<10000;i++){
            new Thread(()->{
                int newId=integerIdIncrement();
                System.out.println(String.format("%s 线程：%s 得到新id: %s",getNowTime(), Thread.currentThread().getName(),newId));
            }).start();;
        }
    }
    AtomicInteger ai=new AtomicInteger();
    public int integerIdIncrement(){        
        int prev,next;
        do{//类似于stampLock的版本化，但是这个只是值化匹配，不是版本匹配
            //版本匹配模式下，修改后虽然值可能相同，但也算脏了需要悲观再读
            //而cas可以在修改后的值与修改前的值一样时，也可以修正，结果等同
            prev=ai.get();
            next=prev+1;            
        }while(!ai.compareAndSet(prev, next));
        return next;
    }


    /*线程安全的类已经内部处理了在多线程场景下【此类范围内】数据对象的安全一致性，所以外部就可以不用过多操心多线程安全问题，直接调用即可
    List	ArrayList	            CopyOnWriteArrayList
    Map	    HashMap	                ConcurrentHashMap
    Set	    HashSet / TreeSet	    CopyOnWriteArraySet
    Queue	ArrayDeque / LinkedList	ArrayBlockingQueue / LinkedBlockingQueue
    Deque	ArrayDeque / LinkedList	LinkedBlockingDeque
    */
    public void threadHandle7(){        
        Map<Integer,String> safeMap=new ConcurrentHashMap<>();
        for(var ii=0;ii<1000;ii++){
            new Thread(()->{
                safeMap.put(Integer.valueOf(10),"aafd");
                safeMap.put(Integer.valueOf(20),"aafd");
                safeMap.remove(Integer.valueOf(20));
                System.out.println("SIZE-----"+safeMap.size());
            }).start();
        }
    }

    CounterTest4 ctt4=new CounterTest4();
    public void threadHandle6(){    
        new Thread(()->{
            try{
                ctt4.read();
            }catch(Exception ex){                    
            }                                
        },String.format("读线程 %s%s%s ",1,1,1)).start();
        try{
            Thread.sleep(1000);//确保读线程先于写线程执行，以便检验脏读场景
        }catch(Exception ex){

        }        
        new Thread(()->{
            try{
                ctt4.write(100);
            }catch(Exception ex){                    
            }                                
        },String.format("写线程 %s%s%s ",1,1,1)).start();
    }
    public class CounterTest4{
        /* StampedLock是不可重入锁 即同一个线程对于同一个stamplock锁对象，不可以多次获取
            因为脏检测无法再重入时进行有效判定！！！
        */
        private StampedLock sl=new StampedLock();
        int total;
        public void write(int val){
            System.out.println(String.format("%s 写方法，获取锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
            var stamp=sl.writeLock();
            try{
                total+=val;
            }finally{
                System.out.println(String.format("%s 写方法，释放锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
                sl.unlock(stamp);
            }
        }
        public int read() throws InterruptedException{
            Thread.sleep(1000);
            var stamp=sl.tryOptimisticRead();
            var a=total;
            System.out.println(String.format("%s 读方法，乐观读锁读到的total=%s  线程：%s",getNowTime(),a, Thread.currentThread().getName()));                
            Thread.sleep(6000);
            if(!sl.validate(stamp)){
                System.out.println(String.format("%s 读方法，获取悲观读锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
                stamp=sl.readLock();
                System.out.println(String.format("%s 读方法，获取悲观读锁后  线程：%s",getNowTime(), Thread.currentThread().getName()));                
                try{
                    a=total;
                    System.out.println(String.format("%s 读方法，悲观读锁读到的total=%s  线程：%s",getNowTime(),a, Thread.currentThread().getName()));                
                }finally{
                    System.out.println(String.format("%s 读方法，释放悲观读锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));                
                    sl.unlockRead(stamp);
                }
            }
            return a;
        }
    }

    public String getNowTime(){
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        return dtf.format(LocalDateTime.now());        
    }
    CounterTest3 ctt3=new CounterTest3();
    /*"读写锁，A线程获取了读锁，B线程尝试获取写锁被阻塞，C线程这时获取读锁会成功么？不会
        jdk的实现中，在获取读锁的时候会检查一下当前被阻塞队列的队首线程是不是在尝试获取写锁，如果是则读写锁会获取失败，进入自选获取状态。
        想来这种方式也合理，避免了写锁被“饿死”的情况。
    */
    public void threadHandle5(){
        new Thread(()->{
            try{
                ctt3.get();
            }catch(Exception ex){                    
            }                                
        },String.format("读线程 %s%s%s ",1,1,1)).start();
        try{
            Thread.sleep(1000);//确保读线程先于写线程执行，以便检验读锁释放阻塞写锁
        }catch(Exception ex){

        }        
        new Thread(()->{
            try{
                ctt3.change(100);
            }catch(Exception ex){                    
            }                                
        },String.format("写线程 %s%s%s ",1,1,1)).start();
        try{
            Thread.sleep(1000);//确保第二个读线程后于写线程执行，以便检验第二读锁是否被有写阻塞的读锁阻塞
        }catch(Exception ex){

        }        
        new Thread(()->{
            try{
                ctt3.get();
            }catch(Exception ex){                    
            }                                
        },String.format("读线程 %s%s%s ",2,2,2)).start();
    }
    public class CounterTest3{
        private final ReadWriteLock lock= new ReentrantReadWriteLock();
        private Lock rl=lock.readLock();
        private Lock wl=lock.writeLock();
        int total;
        public void change(int val){
            System.out.println(String.format("%s 写方法，获取锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
            wl.lock();
            System.out.println(String.format("%s 写方法，获取锁后  线程：%s",getNowTime(), Thread.currentThread().getName()));
            try{
                total+=val;
            }finally{
                System.out.println(String.format("%s 写方法，释放锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
                wl.unlock();
            }
        }
        public int get() throws InterruptedException{
            System.out.println(String.format("%s 读方法，获取锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
            rl.lock();
            System.out.println(String.format("%s 读方法，获取锁后  线程：%s",getNowTime(), Thread.currentThread().getName()));
            try{
                Thread.sleep(9000);//测试读锁释放能阻塞写
                return total;
            }finally{
                System.out.println(String.format("%s 读方法，释放锁前  线程：%s",getNowTime(), Thread.currentThread().getName()));
                rl.unlock();
            }            
        }
    }

    CounterTest2 ctt2=new CounterTest2();
    public void threadHandle4(){
        for(var i=1;i<=10000;i++){            
            new Thread(()->{
                try{
                    ctt2.add();
                }catch(Exception ex){                    
                }                                
            },String.format("线程 %s%s%s ",i,i,i)).start();
        }
    }
    public class CounterTest2{
        private final Lock lock=new ReentrantLock();
        Condition con=lock.newCondition();//锁对象上的条件对象 用于await/signal
        int total;
        public void add() throws InterruptedException{
            //更加安全，可以在一定时间范围内试着获取锁，避免死锁 syncronize那种死等模式下的死锁
            if(lock.tryLock(10,TimeUnit.MICROSECONDS)){
                try{
                    System.out.println(String.format("已获得锁：%s", Thread.currentThread().getName()));
                    total+=1;
                    con.signalAll();//类似于notifyAll
                }finally{
                    System.out.println(String.format("开始释放锁：%s  total= %s", Thread.currentThread().getName(),total));
                    lock.unlock();
                }
            }else{
                System.out.println(String.format("放弃了：%s", Thread.currentThread().getName()));
            }
        }
        public void sub()throws InterruptedException{
            if(lock.tryLock(10,TimeUnit.MICROSECONDS)){
                try{
                    System.out.println(String.format("已获得锁：%s", Thread.currentThread().getName()));
                    while(total<=0){
                        con.await();//类似于wait 调用时释放锁，返回时需要再次抢夺锁
                    }                    
                    total-=1;
                }finally{
                    System.out.println(String.format("开始释放锁：%s  total= %s", Thread.currentThread().getName(),total));
                    lock.unlock();
                }
            }else{
                System.out.println(String.format("放弃了：%s", Thread.currentThread().getName()));
            }
        }
    }

    /*演示慢生产，快消费的线程 等待和唤醒的场景样例
    */
    MyQueue testQuene=new MyQueue();
    public void threadHandle3(){    
        for(var i=1;i<=3;i++){
            var s=String.valueOf(i);
            new Thread(()->{
                try{
                    Thread.sleep(100);//慢生产
                    testQuene.product(s);
                }catch(Exception ex){                    
                }                                
            },String.format("生产者 %s%s%s ",i,i,i)).start();
        }
        for(var i=1;i<=3;i++){            
            new Thread(()->{
                try{                    
                    testQuene.comsume();//快消费
                }catch(Exception ex){                    
                }                                
            },String.format("消费者 %s%s%s ",i,i,i)).start();
        }
    }
    public class MyQueue{
        List<String> queue=new ArrayList<>();
        public boolean product(String thing){
            synchronized(this){
                System.out.println(String.format(" 进入生产临界区 当前线程:%s，内容：%s ", Thread.currentThread().getName(),thing));
                queue.add(thing);
                System.out.println(String.format(" 唤醒沉睡者前 当前线程:%s ", Thread.currentThread().getName()));
                this.notifyAll();/* 必须在已被syn的锁对象上调用notify，这才有意义  */
                System.out.println(String.format(" 唤醒沉睡者后syn块内 当前线程:%s ", Thread.currentThread().getName()));
            }
            System.out.println(String.format(" 唤醒沉睡者syn块外释放了this锁 当前线程:%s ", Thread.currentThread().getName()));
            return true;
        }
        //wait方法的InterruptedException异常应该传递给外层来处理！！
        public String comsume() throws InterruptedException{
            var result="";
            synchronized(this){
                System.out.println(String.format(" 进入消费临界区 当前线程:%s ", Thread.currentThread().getName()));
                while(this.queue.isEmpty()){
                    System.out.println(String.format(" 空集进入waiting前 当前线程:%s ", Thread.currentThread().getName()));
                    /*必须在已被syn后的锁对象上调用wait，才有意义！！！
                      执行wait后，当前线程就会释放掉已持有的this锁对象，自己进入waiting状态，等待唤醒；
                      切记，因为wait方法内嵌在syn块内，syn块内要想执行代码指令就必须获得到响应的锁对象此处为this
                            因此，一旦此waiting状态的线程被唤醒后，就立刻必须再次抢夺锁对象，同syn入口处一样！！
                            抢到就挂着锁继续执行，未抢到则进入阻塞态！
                    */
                    this.wait();
                    System.out.println(String.format(" 被唤醒且抢到了this锁了 当前线程:%s ", Thread.currentThread().getName()));                
                }
                System.out.println(String.format(" 非空下开始领取消费 当前线程:%s ", Thread.currentThread().getName()));
                result=this.queue.remove(0);                
            }
            System.out.println(String.format(" 领取消费后已出syn块，释放了this锁 当前线程:%s ", Thread.currentThread().getName()));
            return result;
        }
    }

        
    public class Counter2 {
        private int count = 0;    
        private int another = 0;    
        public synchronized void add(int n) {
            if (n < 0) {
                dec(-n);//可重入获取到同一把锁，即锁可以有持有次数记录，解锁要解到0，才可以被释放
            } else {
                count += n;
            }
        }    
        public synchronized void dec(int n) {
            count += n;
        }
        
        /*死锁  一个先后需要持有 lockA/lockB锁
                另一个先后需要持有 lockB/lockA锁
            就有可能使得一个线程进入第一个持有上lockA 后被中断后
                      另一个线程进入另一个持有上lockB 后，需要获取lockA但是已被上一个线程拿到     
        此时，两个线程各自持有不同的锁，然后各自试图获取对方手里的锁，造成了双方无限等待下去，这就是死锁。
        死锁发生后，没有任何机制能解除死锁，只能强制结束JVM进程。 
        那么我们应该如何避免死锁呢？答案是：线程获取锁的顺序要一致。即严格按照先获取lockA，再获取lockB的顺序          
        */
        public void add2222(int m) {
            synchronized(this) { // 获得lockA的锁
                this.count+= m;
                synchronized(String.class) { // 获得lockB的锁
                    this.another += m;
                } // 释放lockB的锁
            } // 释放lockA的锁
        }
        
        public void dec2222(int m) {
            synchronized(String.class) { // 获得lockB的锁
                this.another -= m;
                synchronized(this) { // 获得lockA的锁
                    this.count -= m;
                } // 释放lockA的锁
            } // 释放lockB的锁
        }
    }


    /*线程安全
        如果一个类被设计为允许多线程正确访问，我们就说这个类就是“线程安全”的（thread-safe），
        下面的Counter类就是线程安全的。Java标准库的java.lang.StringBuffer也是线程安全的。
        还有一些不变类，例如String，Integer，LocalDate，它们的所有成员变量都是final，
        多线程同时访问时只能读不能写，这些不变类也是线程安全的。
        最后，类似Math这些只提供静态方法，没有成员变量的类，也是线程安全的。
        
        同步只保证多线程执行的synchronized块是依次执行，最终状态对不对还取决于你的逻辑。
    */
    class Counter {
        private int count = 0;    
        public void add(int n) {
            //通过this锁定，其实是在类范围内保证在多线程下的类范围内的变量存取原子性和安全性
            //所以称 这个类是线程安全的，
            //当然如果这个类还包含静态方法，为保证静态方法内变量在多线程场景的安全，
            //   也需要通过锁定此类的Class对象class，来保证此类的线程安全
            synchronized(this) {
                count += n;
            }
        }    
        /*当我们锁住的是this实例时，实际上可以用synchronized修饰这个方法。两种写法是等价的*/
        public synchronized void add1(int n) {
            count += n;
        }    
        public void dec(int n) {
            synchronized(this) {
                count -= n;
            }
        }    
        public synchronized void dec1(int n) {
            count -= n;
        }    
        public int get() {//它没有同步，因为读一个int变量不需要同步。
            return count;
        }        
        public synchronized Person88 get2() {//多赋值语句，就必须要同步了。
            var p=new Person88("ss");
            p.name="aaaaaaa";
            return p;
        }
    }
    //对于static方法，是没有this实例的，因为static方法是针对类而不是实例。
    //但是我们注意到任何一个类都有一个由JVM自动创建的Class实例，
    //因此，对static方法添加synchronized，锁住的是该类的Class实例。
    public synchronized static void test(int n) {
        synchronized(App.class) {
            
        }
    }

    /*共享变量进行读取和写入时，结果要正确，必须保证是原子操作。原子操作是指不能被中断的一个或一系列操作
      一个语句可能是多个cpu指令单元组成，多线程被无序调度造成数据不一致问题，同时load然后各自加减造成不一致
      概括一下如何使用synchronized：
        找出修改共享变量的线程代码块；
        选择一个共享实例作为锁； 一定是同一把锁，切记让其是静态不可变的
        使用synchronized(lockObject) { ... }。
        在使用synchronized的时候，不必担心抛出异常。因为无论是否有异常，都会在synchronized结束处正确释放锁：
    */
    public static final Object lockerForDataX=new Object();
    public static int dataX=0;    
    public void threadHandle2(){
        var t1=new Thread(()->{
            for(var i=0;i<123456;i++){
                synchronized(lockerForDataX){                    
                    dataX++;  
                    if(i==123450)
                        throw new RuntimeException("11");    
                }//无论有无异常，都会在此释放锁 
                //dataX++;                            
            }
        });
        var t2=new Thread(()->{
            for(var i=0;i<123456;i++){
                synchronized(lockerForDataX){                   
                    dataX--;//加锁后，保证了dataX--语句对应的指令集同时只能对一个线程有效，不会被多个线程穿插执行！！保证了指令集的原子性！！
                    if(i==123450)
                        throw new RuntimeException("22");           
                }//无论有无异常，都会在此释放锁
                //dataX--;               
            }
        });
        t1.start();
        t2.start();
        try{
            t1.join();
            t2.join();
            System.out.println(dataX);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }    
        /*JVM规范定义了几种原子操作：
            基本类型（long和double除外）赋值，例如：int n = m；
            引用类型赋值，例如：List<String> list = anotherList。
            long和double是64位数据，JVM没有明确规定64位赋值操作是不是一个原子操作，
            不过在x64平台的JVM是把long和double的赋值作为原子操作实现的。
            
            
            public void set(int m,String s) {
                synchronized(lock) {//此处没有必要进行同步锁定
                    this.value = m; //因为value是基本类型，且语句是赋值语句
                }
                synchronized(lock) {//此处没有必要进行同步锁定
                    this.value = s; //因为value是引用类型，且语句是赋值语句
                }
            }
        */    
    }

    /* 主线程或入口线程结束后，如果还有其开启的子线程未结束，默认情况下，jvm进程不会退出！！！
    */
    public void threadHandle1(){
        var t=new Thread(()->{
            try{
                System.out.println("子线程开始111111111111");
                Thread.sleep(5000);
                System.out.println("子线程结束111111111111");
            }catch(Exception ex){
                System.out.println("子线程中发生异常："+ex.toString());
            }            
        });
        try{
            //t.join();//放在前面根本不能锁定
            t.start();
            //t.join(10000);//可以同步等待子线程终止后再继续执行 可设置最大等待时间
        }catch(Exception ex){
            System.out.println("主线程中发生异常："+ex.toString());
        }   
        
        /*中断一个线程非常简单，只需要在其他线程中对目标线程调用interrupt()方法，
          目标线程需要反复检测自身状态是否是interrupted状态，如果是，就立刻结束运行。
        */
        var t1=new Thread(()->{
            try{
                System.out.println("子线程开始22222222");
                while(!Thread.currentThread().isInterrupted()){//获取当前线程栈中的标记信息
                    System.out.println("子线程222运行中");
                }
                System.out.println("子线程结束222222");
            }catch(Exception ex){
                System.out.println("子线程222222中发生异常："+ex.toString());
            }            
        });
        
        //t1.join();//放在前面根本不能锁定
        t1.start();
        try{
            Thread.sleep(2000);
        }catch(InterruptedException ex){
            System.out.println("主线程中被外部中断了");
        }   

        System.out.println("主线程给子线程222发中断信号");
        t1.interrupt();//设置目标线程栈中的中断标记
        /*如果线程处于等待状态，例如，t.join()会让main线程进入等待状态，
            此时，如果对main线程调用interrupt()，join()方法会立刻抛出InterruptedException，
            因此，目标线程只要捕获到join()方法抛出的InterruptedException，
            就说明有其他线程对其调用了interrupt()方法，通常情况下该线程应该立刻结束运行。            
        */
        try{
            t1.join(10000);//可以同步等待子线程终止后再继续执行 可设置最大等待时间                
        }catch(InterruptedException ex){
            System.out.println("主线程中被外部中断了"+ex.toString());
        }   
        System.out.println("主线程等待22222完成");
        
        var t2=new MyThread();
        t2.start();
        try{
            Thread.sleep(3000);
            System.out.println("主线程设置MyThread-stopbycall 前");
            t2.stopbycall=true;
            System.out.println("主线程设置MyThread-stopbycall 后");
            t2.join();
        }catch(Exception ex){
            System.out.println(ex.toString());
        }   
        
        /*守护线程，可在jvm退出后继续执行，切记因其可不依赖jvm因此有诸多限制
        */
        var t3=new Thread(()->{
            while (true) {
                System.out.println(LocalTime.now());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        System.out.println("设置守护线程，并启动它");
        t3.setDaemon(true);
        t3.start();
    }

    class MyThread extends Thread{
        //设置 直存 模式，以便线程间同步
        public volatile boolean stopbycall=false;
        public void run(){
            while(!stopbycall){
                try{
                    System.out.println("子线程333333执行中。。。");
                    sleep(1000);
                }catch(InterruptedException ex){
                    System.out.println(ex.toString());
                }                
            }
        }
    }


    /*数字证书可以防止中间人攻击，因为它采用【链式签名认证】，
        即通过根证书（Root CA）去签名下一级证书，这样层层签名，直到最终的用户证书。
        而Root CA证书【内置于操作系统】中，所以，任何经过CA认证的数字证书都可以对其本身进行校验，确保证书本身不是伪造的。
        数字证书存储的是公钥，以及相关的证书链和算法信息。
    以HTTPS协议为例，浏览器和服务器建立安全连接的步骤如下：
        浏览器向服务器发起请求，服务器向浏览器发送自己的数字证书；
        浏览器用操作系统内置的Root CA来验证服务器的证书是否有效，如果有效，就使用该证书加密一个随机的AES口令并发送给服务器；
        服务器用自己的私钥解密获得AES口令，并在后续通讯中使用AES加密。
    */
    public void digitCertificate(){
        try{
            byte[] message = "Hello, use X.509 cert!".getBytes("UTF-8");
            // 读取KeyStore:
            KeyStore ks = loadKeyStore("/my.keystore", "123456");
            //如果部署到Web服务器上，例如Nginx，需要把私钥导出为Private Key格式，把证书导出为X509Certificate格式。
            // 读取私钥:
            PrivateKey privateKey = (PrivateKey) ks.getKey("mycert", "123456".toCharArray());
            // 读取证书:
            X509Certificate certificate = (X509Certificate) ks.getCertificate("mycert");
            // 加密:
            byte[] encrypted = encrypt(certificate, message);
            System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
            // 解密:
            byte[] decrypted = decrypt(privateKey, encrypted);
            System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
            // 签名:
            byte[] sign = sign(privateKey, certificate, message);
            System.out.println(String.format("signature: %x", new BigInteger(1, sign)));
            // 验证签名:
            boolean verified = verify(certificate, message, sign);
            System.out.println("verify: " + verified);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }    
    static KeyStore loadKeyStore(String keyStoreFile, String password) {
        try (InputStream input = App.class.getResourceAsStream(keyStoreFile)) {
            if (input == null) {
                throw new RuntimeException("file not found in classpath: " + keyStoreFile);
            }
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(input, password.toCharArray());
            return ks;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static byte[] encrypt(X509Certificate certificate, byte[] message) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(certificate.getPublicKey().getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
        return cipher.doFinal(message);
    }

    static byte[] decrypt(PrivateKey privateKey, byte[] data) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    static byte[] sign(PrivateKey privateKey, X509Certificate certificate, byte[] message)
            throws GeneralSecurityException {
        Signature signature = Signature.getInstance(certificate.getSigAlgName());
        signature.initSign(privateKey);
        signature.update(message);
        return signature.sign();
    }

    static boolean verify(X509Certificate certificate, byte[] message, byte[] sig) throws GeneralSecurityException {
        Signature signature = Signature.getInstance(certificate.getSigAlgName());
        signature.initVerify(certificate);
        signature.update(message);
        return signature.verify(sig);
    }
    

    /*签名实际上并不是针对原始消息，而是针对原始消息的哈希进行签名
      signature = encrypt(privateKey, sha256(message))
      对签名进行验证实际上就是用公钥解密
      hash = decrypt(publicKey, signature)
    */
    public void digitSigner(){
        try{
            // 生成RSA公钥/私钥:
            var kpGen = java.security.KeyPairGenerator.getInstance("RSA");
            kpGen.initialize(1024);
            var kp = kpGen.generateKeyPair();
            var sk = kp.getPrivate();
            var pk = kp.getPublic(); 
            // 待签名的消息:
            byte[] message = "Hello, I am Bob!".getBytes(StandardCharsets.UTF_8); 
            // 用私钥签名:
            var s = java.security.Signature.getInstance("SHA1withRSA");
            s.initSign(sk);
            s.update(message);
            byte[] signed = s.sign();
            System.out.println(String.format("signature: %x", new BigInteger(1, signed)));
    
            // 用公钥验证:
            var v = java.security.Signature.getInstance("SHA1withRSA");
            v.initVerify(pk);
            v.update(message);
            boolean valid = v.verify(signed);
            System.out.println("valid? " + valid);
        }catch(Exception ex){
            System.out.println(ex.toString());
        }         
    }

    /* 加盐防止各种彩虹表查表反推出密码，前提是已被拖库；
       密码获取后就可以 拿着用户名+密码 去各大网站去撞库
       彩虹表就是根据一些常见密码口令以及常见哈希算法 预建的表
       如果加盐后尤其是加上随机盐后，就使得这个常规预建表失效，就算是同时知道了盐，但是会使得表指数级膨胀
       所以随机盐可以直接 通过各种规律 插入原有哈希后的内容中即可！头部或尾部或奇数位等
    */
    public void cyproHandle(){
        try{
            var keyGen = javax.crypto.KeyGenerator.getInstance("HmacMD5");
            var key = keyGen.generateKey();
            //存储skey，以便后期验证时使用
            byte[] skey = key.getEncoded();
            System.out.println(new BigInteger(1, skey).toString(16));
            var mac = javax.crypto.Mac.getInstance("HmacMD5");
            mac.init(key);
            mac.update("HelloWorld".getBytes("UTF-8"));
            byte[] result = mac.doFinal();
            System.out.println(new BigInteger(1, result).toString(16));

            //通过存储的key字节数组重建key对象
            var key2 = new javax.crypto.spec.SecretKeySpec(skey, "HmacMD5");
            byte[] skey2 = key2.getEncoded();
            System.out.println(new BigInteger(1, skey2).toString(16));
            var mac2 = javax.crypto.Mac.getInstance("HmacMD5");
            mac2.init(key2);
            mac2.update("HelloWorld".getBytes("UTF-8"));
            byte[] result2 = mac2.doFinal();
            System.out.println(new BigInteger(1, result2).toString(16));
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }

    public void thirdCyprolib(){   
        try{
            System.out.println("注册第三方加解密库");
            //注册只需要在启动时进行一次，后续就可以使用BouncyCastle提供的所有哈希算法和加密算法。     
            java.security.Security.addProvider(new BouncyCastleProvider());        
            MessageDigest md = MessageDigest.getInstance("RipeMD160");
            md.update("HelloWorld".getBytes("UTF-8"));
            byte[] result = md.digest();
            System.out.println(new BigInteger(1, result).toString(16));
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }


    /*URL编码是浏览器发送数据给服务器时使用的编码，它通常附加在URL的参数部分，例如：
        https://www.baidu.com/s?wd=%E4%B8%AD%E6%96%87
        之所以需要URL编码，是因为出于兼容性考虑，很多服务器只识别ASCII字符。但如果URL中包含中文、日文这些非ASCII字符怎么办？不要紧，URL编码有一套规则：

        如果字符是A~Z，a~z，0~9以及-、_、.、*，则保持不变；
        如果是其他字符，先转换为UTF-8编码，然后对每个字节以%XX表示。
        例如：字符 中 的UTF-8编码是0xe4b8ad，因此，它的URL编码是%E4%B8%AD。URL编码总是大写。
    */
    public void URLEncode(){
        //中的URL编码是%E4%B8%AD，文的URL编码是%E6%96%87，!虽然是ASCII字符，也要对其编码为%21。
        String encoded = URLEncoder.encode("%中 文+!", StandardCharsets.UTF_8);
        //和标准的URL编码稍有不同，URLEncoder把空格字符编码成+，
        //而现在的URL编码标准要求空格被编码为%20，不过，服务器都可以处理这两种情况
        System.out.println(encoded);

        String decoded = URLDecoder.decode(encoded, StandardCharsets.UTF_8);
        System.out.println(decoded);

        /*而Base64编码是对二进制数据进行编码，表示成安全的字符格式，即将可能包含控制符区域的二进制数据转换为安全的字符。        
            Base64编码可以把任意长度的二进制数据变为纯文本，且只包含A~Z、a~z、0~9、+、/ 这些字符。
            它的原理是把3字节的二进制数据按6bit一组，用4个int整数表示，
            然后查表，把int整数用索引对应到字符，得到编码后的字符串。
            因为6位整数的范围总是0~63，所以，能用64个字符表示：
            字符A~Z对应索引0~25，字符a~z对应索引26~51，字符0~9对应索引52~61，
            最后两个索引62、63分别用字符+和/表示。
        */
        byte[] input = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad };
        String b64encoded = Base64.getEncoder().encodeToString(input);
        System.out.println(b64encoded);

        byte[] output = Base64.getDecoder().decode(b64encoded);
        System.out.println(Arrays.toString(output)); // [-28, -72, -83]
        /*如果输入的byte[]数组长度不是3的整数倍肿么办？这种情况下，需要对输入的末尾补一个或两个0x00，
            编码后，在结尾加一个=表示补充了1个0x00，加两个=表示补充了2个0x00，
            解码的时候，去掉末尾补充的一个或两个0x00即可。
            实际上，因为编码后的长度加上=总是4的倍数，所以即使不加=也可以计算出原始输入的byte[]。
            Base64编码的时候可以用withoutPadding()去掉=，解码出来的结果是一样的：        
        */
        byte[] input2 = new byte[] { (byte) 0xe4, (byte) 0xb8, (byte) 0xad, 0x21 };
        String b64encoded2 = Base64.getEncoder().encodeToString(input2);
        String b64encoded3 = Base64.getEncoder().withoutPadding().encodeToString(input2);
        System.out.println(b64encoded2);
        System.out.println(b64encoded3);
        byte[] output2 = Base64.getDecoder().decode(b64encoded2);
        System.out.println(Arrays.toString(output2));
        byte[] output3 = Base64.getDecoder().decode(b64encoded3);
        System.out.println(Arrays.toString(output3));

        /*因为标准的Base64编码会出现+、/和=，所以不适合把Base64编码后的字符串放到URL中。
        一种针对URL的Base64编码可以在URL中使用的Base64编码，它仅仅是把+变成-，/变成_：
        */
        byte[] input5 = new byte[] { 0x01, 0x02, 0x7f, 0x00 };
        String b64encoded5 = Base64.getUrlEncoder().withoutPadding().encodeToString(input5);
        System.out.println(b64encoded5);
        byte[] output5 = Base64.getUrlDecoder().decode(b64encoded5);
        System.out.println(Arrays.toString(output5));

        try{
            // 创建一个MessageDigest实例:
            MessageDigest md = MessageDigest.getInstance("MD5");
            // 反复调用update输入数据:
            /*“盐值 salt”是在加密过程中生成的随机字符串；
                可以将salt放到passWord前面作为前缀或放到passWord后面作为后缀得到新的字符串PS，
                即，PS = password和salt的组合串;
            */
            md.update("撒点盐避免彩虹表反推".getBytes("UTF-8"));
            md.update("口令密码".getBytes("UTF-8"));
            byte[] result = md.digest(); // 16 bytes
            System.out.println(new BigInteger(1, result).toString(16));

            // 创建一个MessageDigest实例:
            MessageDigest md2 = MessageDigest.getInstance("SHA-1");
            // 反复调用update输入数据:            
            md2.update("撒点盐避免彩虹表反推".getBytes("UTF-8"));
            md2.update("口令密码".getBytes("UTF-8"));
            byte[] result2 = md2.digest(); // 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2
            System.out.println(new BigInteger(1, result2).toString(16));
            
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        

    }
    
    /* 小文件直接使用java.nio.Files工具类进行操作
        基本方法已内置try(resouce)了，可以放心使用
    */
    public void ioHandle91(){
        try{           
            // 写入二进制文件:
            byte[] data2 = new byte[]{ 51,52,53 };
            Files.write(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new6.txt"), data2);
            // 写入文本并指定编码:
            Files.writeString(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new7.txt"), "文本内容llklk...", StandardCharsets.UTF_8);
            // 按行写入文本:
            List<String> lines2 = new ArrayList<>();
            lines2.add("kjkjkjk");
            lines2.add("中国开卷考就看");
            Files.write(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new8.txt"), lines2);

            byte[] data = Files.readAllBytes(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new3.txt"));
            System.out.println(data);
            // 默认使用UTF-8编码读取:
            String content1 = Files.readString(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new3.txt"));
            System.out.println(content1);
            // 可指定编码:
            String content2 = Files.readString(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new3.txt"), StandardCharsets.UTF_8);
            System.out.println(content2);
            // 按行读取并返回每行内容:
            List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\sunsea\\Desktop\\tmp\\new3.txt"));
            System.out.println(lines.get(0));

        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }

    /*PrintStream是一种FilterOutputStream PrintStream和OutputStream相比，除了添加了一组print()/println()方法，可以打印各种数据类型，比较方便外，
        它还有一个额外的优点，就是不会抛出IOException，这样我们在编写代码的时候，就不必捕获IOException。
        System.out是系统默认提供的PrintStream，表示标准输出;  System.err是系统默认提供的标准错误输出

    */
    public void ioHandle9(){
        StringWriter buffer = new StringWriter();
        try (PrintWriter pw = new PrintWriter(buffer)) {
            pw.println("Hello");
            pw.println(12345);
            pw.println(true);
        }
        System.out.println(buffer.toString());
    }

    /*Reader是带编码转换器的InputStream，它把byte转换为char，而Writer就是带编码转换器的OutputStream，它把char转换为byte并输出。
        写入一个字符（0~65535）：void write(int c)；
        写入字符数组的所有字符：void write(char[] c)；
        写入String表示的所有字符：void write(String s)。
        具体实现：FileWriter / CharArrayWriter / StringWriter
        OutputStreamWriter 通用转换器
    */
    public void ioHandle8(){
        try (Writer writer = new FileWriter("C:\\Users\\sunsea\\Desktop\\tmp\\new3.txt", StandardCharsets.UTF_8)) {
            writer.write('H'); // 写入单个字符
            writer.write("z找客户看开卷考就看".toCharArray()); // 写入char[]
            writer.write("Hello"); // 写入String
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        try (CharArrayWriter writer = new CharArrayWriter()) {
            writer.write(65);
            writer.write(66);
            writer.write(67);
            System.out.println(writer.toCharArray()); // { 'A', 'B', 'C' }
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        try (StringWriter writer = new StringWriter()) {    
            writer.write("艰苦艰苦就");
            writer.write("kkkkkk");        
            System.out.println(writer.toString()); // { 'A', 'B', 'C' }
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        try (Writer writer = new OutputStreamWriter(new FileOutputStream("C:\\Users\\sunsea\\Desktop\\tmp\\new4.txt"), "UTF-8")) {
            writer.write("艰苦艰苦就");
            writer.write("kkkkkk");
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 
        
    }

    /*Reader是Java的IO库提供的另一个输入流接口。
      和InputStream的区别是，InputStream是一个字节流，即以byte为单位读取，而Reader是一个字符流，即以char为单位读取
      这个方法读取字符流的下一个字符，并返回字符表示的int，范围是0~65535。如果已读到末尾，返回-1。
      java.io.Reader是所有字符输入流的超类,具体实现FileReader
      
      读取文本内容的流时一定要用jdk提供的字符流处理类！！！不要自己用字节流带缓存模式的自解码处理！！！
      java 中的字符流处理类Reader/Writer已经考虑到双char字符场景，所以直接使用即可！只要保证字符数组缓存区的长度大于2即可！！
    */
    public void ioHandle7(){
        try(var fr=new FileReader("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt", StandardCharsets.UTF_8)){
            // for(;;){
            //     int n =fr.read();
            //     if(n==-1) break;
            //     System.out.println((char)n);
            // }
            char[] buffer = new char[1000];
            int n;
            while ((n = fr.read(buffer)) != -1) {
                System.out.println("read " + n + " chars.");
                for(var ii=0;ii<n;ii++)
                    System.out.print((char)buffer[ii]);
            }
            System.out.println();
            
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        //内存字符流
        try (Reader reader = new CharArrayReader("Hello".toCharArray())) {
            char[] buffer = new char[1000];
            int n;
            while ((n = reader.read(buffer)) != -1) {
                System.out.println("read " + n + " chars.");
                for(var ii=0;ii<n;ii++)
                    System.out.print((char)buffer[ii]);
            }
            System.out.println();
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        //内存字符流
        try (Reader reader = new StringReader("浙啊多发点33高考监考")) {
            char[] buffer = new char[1000];
            int n;
            while ((n = reader.read(buffer)) != -1) {
                System.out.println("read " + n + " chars.");
                for(var ii=0;ii<n;ii++)
                    System.out.print((char)buffer[ii]);
            }
            System.out.println();
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        /*除了特殊的CharArrayReader和StringReader，普通的Reader实际上是基于InputStream构造的，
            因为Reader需要从InputStream中读入字节流（byte），然后，根据编码设置，再转换为char就可以实现字符流。
            如果我们查看FileReader的源码，它在内部实际上持有一个FileInputStream。        
        */
        try (Reader reader = new InputStreamReader(new FileInputStream("C:\\Users\\sunsea\\Desktop\\tmp\\new2.txt"), "GBK")) {
            char[] buffer = new char[1000];
            int n;
            while ((n = reader.read(buffer)) != -1) {
                System.out.println("read " + n + " chars.");
                for(var ii=0;ii<n;ii++)
                    System.out.print((char)buffer[ii]);
            }
            System.out.println();
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

        try(var sr2=new InputStreamReader(new ByteArrayInputStream("😥😥".getBytes()))){
            var buf=new char[3];//buf长度至少等于2，以便能显示最大的unicode字符！！                
            var n=0;
            //read会自动将末尾单高代理字符押给下次read结果中，即将双char字符派送在一次结果缓存中
            while((n=sr2.read(buf,0,buf.length))!=-1){
                System.out.println(String.valueOf(buf,0,n)+"---1111");
            }                
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 

    }



    /*常规的程序启动运行时获取配置文件信息，基于classpath的    
    */
    public void ioHandle6(){
        Properties props = new Properties();
        try (InputStream input = getClass().getResourceAsStream("/log4j.properties");
            ) {
            //我们把默认的配置放到jar包中，再从外部文件系统读取一个可选的配置文件，
            //就可以做到既有默认的配置文件，又可以让用户自己修改配置：
            if (input != null) {                
                props.load(input);                
            }
            var f=new File("./conf.properties");
            if(f.exists()){
                try(InputStream input2 =new FileInputStream("./conf.properties")){
                    props.load(input2);
                }                
            }            
        }catch(Exception ex){
            System.out.println(ex.toString());
        } 
    }

    /*zip jar InputStream/OutputStream 是装饰类    
    */
    public void ioHandle5(){
        try (ZipInputStream zip = new ZipInputStream(new FileInputStream("C:\\Users\\sunsea\\Desktop\\tmp\\test.zip"))) {
            ZipEntry entry = null;
            while ((entry = zip.getNextEntry()) != null) {//遍历所有层级
                String name = entry.getName();
                System.out.println(name);
                if (!entry.isDirectory()) {
                    int n;                      
                    while ((n = zip.read()) != -1) {
                        System.out.println(n);
                    }
                }
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
        try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream("C:\\Users\\sunsea\\Desktop\\tmp\\test22.zip"))) {
            zip.putNextEntry(new ZipEntry("Root/"));//目录项以/结尾    
            zip.closeEntry();
            zip.putNextEntry(new ZipEntry("Root/subdir/")); //目录项以/结尾          
            zip.closeEntry();
            zip.putNextEntry(new ZipEntry("Root/subdir/1.txt"));         
            zip.write("中国人民万岁".getBytes("utf-8"));   
            zip.closeEntry();
        }catch(Exception ex){
            System.out.println(ex.toString());
        }

    }

    /*通过装饰模式（组合）化解对于各种流处理需求基于继承的子类爆炸问题！    
        传入基础类实例，然后装饰类对传入的类实例进行组合式装饰！！
    */
    public void ioHandle4(){
        try{
            byte[] data="中国人民万岁".getBytes("utf-8");
            try(ReadCountDecorate input =new ReadCountDecorate(new ByteArrayInputStream(data))){
                for(;;){
                    var n=input.read();
                    if(n==-1) break;
                    System.out.println(n);
                }
                System.out.println("读取的字节总数：--"+input.getReadCount());
            }catch(Exception ex){
                System.out.println(ex.toString());
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    class ReadCountDecorate extends FilterInputStream{
        
        public ReadCountDecorate(InputStream in){
            super(in);
        }
        int count;
        
        public int getReadCount(){
            return this.count;
        }

        @Override
        public int read() throws IOException {
            int n = in.read();
            if (n != -1) {
                this.count ++;
            }
            return n;
        }    
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int n = in.read(b, off, len);
            if (n != -1) {
                this.count += n;
            }
            return n;
        }
    }

    /*OutputStream也是抽象类，它是所有输出流的超类。这个抽象类定义的一个最重要的方法就是void write(int b)
        要注意的是，虽然传入的是int参数，但只会写入一个字节，即只写入int最低8位表示字节的部分（相当于b & 0xff）。
        出于效率的考虑，操作系统并不是输出一个字节就立刻写入到文件或者发送到网络，
        而是把输出的字节先放到内存的一个缓冲区里（本质上就是一个byte[]数组），等到缓冲区写满了，再一次性写入文件或者网络。
        通常情况下，我们不需要调用这个flush()方法，因为缓冲区写满了OutputStream会自动调用它，
        并且，在调用close()方法关闭OutputStream之前，也会自动调用flush()方法。
        但是，在某些情况下，我们必须手动调用flush()方法;例如即时聊天场景中！解决办法就是每输入一句话后，立刻调用flush()
        实际上，InputStream也有缓冲区。例如，从FileInputStream读取一个字节时，操作系统往往会一次性读取若干字节到缓冲区，
        并维护一个指针指向未读的缓冲区。然后，每次我们调用int read()读取下一个字节时，可以直接返回缓冲区的下一个字节，
        避免每次读一个字节都导致IO操作。当缓冲区全部读完后继续调用read()，则会触发操作系统的下一次读取并再次填满缓冲区。

    */
    public void ioHandle3(){
        try(OutputStream output = new FileOutputStream("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt");
            ByteArrayOutputStream output2 = new ByteArrayOutputStream()){
            output.write("中国".getBytes("utf-8"));        
            ///////////////////////
            byte[] data;        
            output2.write("中国".getBytes("utf-8"));
            output2.write("美丽".getBytes("utf-8"));
            data=output2.toByteArray();
            System.out.println(new String(data, "UTF-8"));
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }

    /* InputStream并不是一个接口，而是一个抽象类，它是所有输入流的超类。这个抽象类定义的一个最重要的方法就是int read()
       这个方法会读取输入流的下一个字节，并返回字节表示的int值（0~255）。如果已读到末尾，返回-1表示不能继续读取了。
       具体实现FileInputStream实现了文件流输入；ByteArrayInputStream在内存中模拟一个字节流输入。
       总是使用try(resource)来保证InputStream正确关闭。编译器只看try(resource = ...)中的对象是否实现了java.lang.AutoCloseable接口，
       如果实现了，就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口，因此，都可以用在try(resource)中。

    */
    public void ioHandle2(){
        try(InputStream input = new FileInputStream("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt")){
            // for (;;) {
            //     int n = input.read(); // 反复调用read()方法，直到返回-1
            //     if (n == -1) {
            //         break;
            //     }
            //     System.out.println(n); // 打印byte的值
            // }
            // 定义1000个字节大小的缓冲区:
            byte[] buffer = new byte[3];
            int n;//JAVA 没有定义EOF常量，只能用-1进行判定文件结尾
            while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
                System.out.println("read " + n + " bytes.");
                for(var ii=0;ii<n;ii++){                    
                    System.out.println(buffer[ii]); // 打印byte的值
                }
            }
            byte[] bytes=new byte[]{ 52,53,54 };
            try(InputStream input2 =new ByteArrayInputStream(bytes)){
                for(;;){
                    var n2=input2.read();
                    if(n2==-1) break;
                    System.out.println(n2);
                }
            }catch(Exception ex){
                System.out.println(ex.toString());
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    
    /* IO输入输出以内存为中心的数据的输入输出！Input指从外部读入数据到内存；Output指把数据从内存输出到外部
        IO流以byte（字节）为最小单位，因此也称为字节流。
        使用InputStream，我们读入的数据和原始二进制数据一模一样，是byte[]数组，但是我们可以自己把二进制byte[]数组按照某种编码转换为字符串。
        如果我们需要读写的是字符，并且字符不全是单字节表示的ASCII字符，那么，按照char来读写显然更方便，这种流称为字符流。
        Reader和Writer本质上是一个能自动编解码的InputStream和OutputStream。
        使用Reader，数据源虽然是字节，但我们读入的数据都是char类型的字符，原因是Reader内部把读入的byte做了解码，转换成了char。

        InputStream、OutputStream、Reader和Writer都是同步IO的抽象类，
        对应的具体实现类，以文件为例，有FileInputStream、FileOutputStream、FileReader和FileWriter。
    */
    public void ioHandle(){
        try{
            var aa="hi你好".getBytes();
            var bb="hi你好".getBytes("utf-8");
            System.out.println(bb.length);
            
            /*构造File对象时，既可以传入绝对路径，也可以传入相对路径。绝对路径是以根目录开头的完整路径
                传入相对路径时，相对路径前面加上当前目录就是绝对路径：
            */
            File f = new File("..");
            System.out.println(f.getPath());
            System.out.println(f.getAbsolutePath());
            System.out.println(f.getCanonicalPath());
            System.out.println(File.separator); // 根据当前平台打印"\"或"/"

            //对目录而言，是否可执行表示能否列出它包含的文件和子目录。
            System.out.println(f.isFile());
            System.out.println(f.isDirectory());
            File file = new File("C:\\Users\\sunsea\\Desktop\\tmp\\new.txt");
            if (file.createNewFile()) {
                // 文件创建成功:
                // TODO:
                if (file.delete()) {
                    // 删除文件成功:
                }
            }
            File tmpf = File.createTempFile("tmp-", ".txt"); // 提供临时文件的前缀和后缀
            tmpf.deleteOnExit(); // JVM退出时自动删除
            System.out.println(tmpf.isFile());
            System.out.println(tmpf.getAbsolutePath());

            File file2 = new File("C:\\Users\\sunsea\\Desktop\\tmp");            
            File[] fs2 = file2.listFiles(new FilenameFilter() { // 仅列出
                public boolean accept(File dir, String name) {
                    return name.endsWith(".txt"); // 返回true表示接受该文件
                }
            });
            if(fs2!=null){
                for (File f4 : fs2) {
                    System.out.println(f4);
                }
            }

            File dir3 = new File("C:\\Users\\sunsea\\Desktop\\tmp\\newDir3\\newDir2");  
            if (dir3.mkdirs()) {//创建当前File对象表示的目录，并在必要时将不存在的父目录也创建出来；
                if (dir3.delete()) {//删除当前File对象表示的目录，当前目录必须为空才能删除成功。
                    
                }
            }
            
        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }

    /* iterable 接口 提供一个迭代器iterator
       iterator 接口 约定hasNext next方法    
    */
    public void collectionHandle91(){
        var rl=new ReverseList<String>();
        rl.add("aaa");
        rl.add("bbb");
        rl.add("ccc");
        rl.add("ddd");
        for(var s : rl){//按照索引顺序倒序遍历
            System.out.println(s);
        }

        /*Collections工具类 基本都是静态方法           */
        List<String> list1 = List.of();//等价 不可变空集合
        List<String> list2 = Collections.emptyList();
        /*在写方法的时候可能结果集不存在，需要返回null,在调用这个方法的地方就要做一个null判断,很繁琐，容易出问题，
        这个时候就可以使用emptyList或EMPTY_LIST。
        但是也会有同学说我new ArrayList不就可以了，这样是可以，
        但是每次我们new 一个集合对象的时候都会有一个初始化空间，占用内存资源，积少成多会浪费很多的资源，
        Collections中的空集合对象是一个静态常量，在内存中只存在一份，所以能够节省内存资源。
        注意：我们在使用emptyList空的方法返回空集合的时候要注意，这个空集合是不可变的。

        比如有一个方法是执行一些逻辑返回一个list, 
        比如查询数据库返回查询结果. 那么当没有任何东西时, 有两个选择: 返回null, 或者返回emptyList. 
        如果直接返回null, 那么调用方就需要判空,否则可能出现NullPointerExp异常, 而返回emptyList, 调用方可以直接操作.
        这些方法就是"空对象设计模式"的一种实现. 和java 8引入的 Optional 比较像.
        */
        List<String> el=Collections.emptyList();
        List<String> newList=new ArrayList<String>();
        System.out.println(el==newList);

        /*创建一个单例元素的List：List<T> singletonList(T o)
            创建一个元素的Map：Map<K, V> singletonMap(K key, V value)
            创建一个元素的Set：Set<T> singleton(T o)
            要注意到返回的单元素集合也是不可变集合，无法向其中添加或删除元素。        
        */
        var p8=new Person88("zzzz");        
        List<Person88> ss=Collections.singletonList(p8);
        //ss.add("bbb");//编译报异常        
        System.out.println(ss.get(0).name);
        p8.name="88888888888888";//集合本身是单元素模式
        System.out.println(ss.get(0).name);        
        
    }
    class ReverseList<T> implements Iterable<T>{
        List<T> list =new ArrayList<T>();
        public void add(T val){
            list.add(val);
        }
        @Override
        public Iterator<T> iterator(){
            return new ReverseIterator(this.list.size());
        }
        class ReverseIterator implements Iterator<T>{            
            int index;
            public ReverseIterator(int index){
                this.index=index;
            }
            @Override
            public boolean hasNext(){
                return this.index>0;
            }
            @Override 
            public T next(){
                this.index--;
                //内部类调用其包装类的实例
                return ReverseList.this.list.get(this.index);
            }
        }
    }

    /*JAVA 中目前以Deque实现后进先出的Stack，因为历史原因有遗留的Stack接口造成的
        把元素压栈：push(E)/addFirst(E)；
        把栈顶的元素“弹出”：pop()/removeFirst()；
        取栈顶元素但不弹出：peek()/peekFirst()。
    */
    public void collectionHandle9(){    
        Deque<String> ss=new LinkedList<>();
        ss.push("abc");
        ss.push("xyz");
        System.out.println(ss.pop());
        System.out.println(ss.pop());
        //System.out.println(ss.pop());//报异常
        System.out.println(ss.size());
        System.out.println(ss.peek());//不报异常 返回null
    }
    /*Queue 队列是先进先出的模式集合，LinkedList实现了该接口
      三对方法，进队/出队带删除/出队不删除 区别是一个是报异常，一个是返回false或null
    */
    public void collectionHandle8(){
        Queue<String> q=new LinkedList<>();
        q.add("abc");
        q.offer("abc");//可重复        
        System.out.println(q.toString());
        q.remove();
        var s=q.poll();
        if (s != null) {//不要把null添加到队列中，否则poll()方法返回null时，很难确定是取到了null元素还是队列为空。
            System.out.println("获取成功");
        } else {
            System.out.println("获取失败");
        }
        System.out.println(q.toString());
        try{
            q.remove();
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
        q.offer("xyz");
        System.out.println(q.element());  
        System.out.println(q.peek());  
        System.out.println(q.element());  
        System.out.println(q.peek());  

        /* 优先级队列，以优先级顺序出队，小的优先级高 compare
           String等常规类型已定义compare
           自定义类需要自设compare
        */
        Queue<String> pq=new PriorityQueue<>();
        pq.add("z");
        pq.add("h");
        pq.add("a");
        System.out.println(pq.toString());
        System.out.println(pq.poll());
        System.out.println(pq.poll());
        System.out.println(pq.poll());
        System.out.println(pq.poll());

        Queue<Person88> pq2=new PriorityQueue<>(new Comparator<Person88>(){
            public int compare(Person88 p1,Person88 p2){
                return p1.name.compareTo(p2.name);
            }
        });
        pq2.add(new Person88("zzzz"));
        pq2.add(new Person88("jjjj"));
        pq2.add(new Person88("aaaa"));
        System.out.println(pq2.toString());
        System.out.println(pq2.poll().name);
        System.out.println(pq2.poll().name);
        System.out.println(pq2.poll().name);
        System.out.println(pq2.poll());

        /*Deque实现了一个双端队列（Double Ended Queue），它可以：
            将元素添加到队尾或队首：addLast()/offerLast()/addFirst()/offerFirst()；
            从队首／队尾获取元素并删除：removeFirst()/pollFirst()/removeLast()/pollLast()；
            从队首／队尾获取元素但不删除：getFirst()/peekFirst()/getLast()/peekLast()；
            总是调用xxxFirst()/xxxLast()以便与Queue的方法区分开；
            避免把null添加到队列。        
        */
        Deque<String> deque = new LinkedList<>();
        deque.offerLast("A"); // A
        deque.offerLast("B"); // A <- B
        System.out.println(deque.toString());
        deque.offerFirst("C"); // C <- A <- B
        System.out.println(deque.toString());
        System.out.println(deque.pollFirst()); // C, 剩下A <- B
        System.out.println(deque.pollLast()); // B, 剩下A
        System.out.println(deque.pollFirst()); // A
        System.out.println(deque.pollFirst()); // null
    }

    /*Set其实是Map的再封装
      HashSet /SortedSet TreeSet
    */
    public void collectionHandle7(){
        Set<String> ss=new HashSet<>();
        System.out.println(ss.add("abc"));
        System.out.println(ss.add("abc"));
        System.out.println(ss.contains("abc"));
        System.out.println(ss.remove("abc"));

        Set<String> ss2=new TreeSet<>();
        System.out.println(ss2.add("abc"));
        System.out.println(ss2.add("abc"));
        System.out.println(ss2.contains("abc"));
        System.out.println(ss2.remove("abc"));
        System.out.println(ss2.add("xyz"));
        System.out.println(ss2.add("ghi"));
        System.out.println(ss2.toString());
    }

    /* java 默认的properties属性文件，#开头为注释，其他都是key=value 切记其从行头搜索第一个=号，后面的都是value
       之前编码必须是Ascii Iso86911格式，中文需用name=\u4e2d\u6587来表示，java9以后可以是utf-8编码
       用Properties读取配置文件，一共有三步：
        创建Properties实例；
        调用load()读取文件；
        调用getProperty()获取配置。
        Java集合库提供的Properties用于读写配置文件.properties。.properties文件可以使用UTF-8编码。
        可以从文件系统、classpath或其他任何地方读取.properties文件。
        读写Properties时，注意仅使用getProperty()和setProperty()方法，不要调用继承而来的get()和put()等方法。
    */
    public void collectionHandle6(){
        try{
            String f = "log4j.properties";
            Properties props = new Properties();
            //props.load(new java.io.FileInputStream(f));
            //props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
            props.load(getClass().getResourceAsStream("/log4j.properties"));            
            System.out.println(props.getProperty("log4j.rootLogger"));

            String settings = "# test" + "\n" + "log4j.rootLogger==Java" + "\n" + "last_open_date=2019-08-07T12:35:01";
            ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
            //如果有多个.properties文件，可以反复调用load()读取，后读取的key-value会覆盖已读取的key-value：
            props.load(input);
            System.out.println(props.getProperty("log4j.rootLogger"));
            System.out.println(props.getProperty("last_open_date"));

            Properties props2 = new Properties();
            props2.setProperty("url", "http://www.liaoxuefeng.com");
            props2.setProperty("language", "Java");//默认以ASCII编码（ISO8859-1）写入
            props2.store(new FileOutputStream("C:\\Users\\sunsea\\Desktop\\tmp\\testjava.properties"), "这是写入的properties注释");            

        }catch(Exception ex){
            System.out.println(ex.toString());
        }        
    }

    /* HashMap内部用equals/hashCode比较，SortedMap接口下的实现TreeMap内部用compareTo比较
    */
    public void collectionHandle5(){
        Map<String,String> tm=new TreeMap<>();
        tm.put("z", "900");
        tm.put("a", "100");
        tm.put("j", "600");//按键compareTo正序遍历
        System.out.println(tm.toString());
        System.out.println(tm.get("a"));//不走hashCode equals 直接通过compareTo比较定位

        Map<Person88,String> tm2 = new TreeMap<>(new Comparator<Person88>(){
            //注意到Person类并未覆写equals()和hashCode()，因为TreeMap不使用equals()和hashCode()
            public int compare(Person88 p1, Person88 p2) {
                return p1.name.compareTo(p2.name);
            }
        });
        tm2.put(new Person88("zhang"), "张");
        tm2.put(new Person88("wang"), "王");
        System.out.println(tm2.toString());
        System.out.println(tm2.get(new Person88("zhang")));//不走hashCode equals 直接通过compareTo比较定位
    }

    //如果Map的key是enum类型，推荐使用EnumMap，既保证速度，也不浪费空间。
    public void collectionHandle4(){
        Map<Weekday,Person99> em = new EnumMap<>(Weekday.class);
        em.put(Weekday.SAT, new Person99("zhang",20));
        em.put(Weekday.FRI, new Person99("wang",30));
        System.out.println(em);
        System.out.println(em.get(Weekday.FRI).name);
    }

    /*Map是可键值映射的存放键值对的集合，键不重复（后值替换覆盖前值，并在替换时返回旧值），值可重复
      HashMap是常用实现 根据内部通过空间换时间的方法，用一个大数组存储所有value，并根据key直接计算出value应该存储在哪个索引：
      HashMap为什么能通过key直接计算出value存储的索引。【两个必要条件，key equals返回true，key对象的hashCode int相等】
      相同的key对象（使用equals()判断时返回true）必须要计算出相同的索引，
      否则，相同的key每次取出的value就不一定对。
      通过key计算索引的方式就是调用key对象的hashCode()方法，它返回一个int整数。
      HashMap正是通过这个方法直接定位key对应的value的索引，继而直接返回value。
      必须保证：
        作为key的对象必须正确覆写equals()方法，相等的两个key实例调用equals()必须返回true；
        作为key的对象还必须正确覆写hashCode()方法，且hashCode()方法要严格遵循以下规范：
            如果两个对象相等，则两个对象的hashCode()必须相等；
            如果两个对象不相等，则两个对象的hashCode()尽量不要相等。
        即对应两个实例a和b：
            如果a和b相等，那么a.equals(b)一定为true，则a.hashCode()必须等于b.hashCode()；
            如果a和b不相等，那么a.equals(b)一定为false，则a.hashCode()和b.hashCode()尽量不要相等。
        上述第一条规范是正确性，必须保证实现，否则HashMap不能正常工作。
        而第二条如果尽量满足，则可以保证查询效率，因为不同的对象，
        如果返回相同的hashCode()，会造成Map内部存储冲突，使存取的效率下降。
      
      切记，Map的遍历都不保证顺序！！

    */
    public void collectionHandle3(){
        Map<String,Person99> sm=new HashMap<>(){};
        var p=new Person99("zhang", 22);
        sm.put("zhang", p);
        var p1=new Person99("liu", 12);
        var oldp=sm.put("zhang", p1);//被移除的老的value对象
        System.out.println(oldp.name);
        var val=sm.get("zhang");//key对应的类也需要实现正确的equals
        System.out.printf("%s %s \n",val==p1,val.name);
        sm.put("ma", p);

        for(var key : sm.keySet()){//按键集合遍历
            System.out.printf("%s %s \n",key,sm.get(key).toString());
        }
        for(var kv : sm.entrySet()){//按键值对集合遍历
            System.out.printf("%s %s \n",kv.getKey(),kv.getValue().toString());
        }

        Map<Person99,String> sm2=new HashMap<>(){};  
        var p2=new Person99("liu", 12); 
        sm2.put(p2, "123");        
        System.out.println(sm2.get(p2));
        var p3=new Person99("liu", 12); //实现了正确的 equals 和 hashCode 
        System.out.println(sm2.get(p3));

        /*实际上HashMap初始化时默认的数组大小只有16，任何key，无论它的hashCode()有多大，都可以简单地通过：
            int index = key.hashCode() & 0xf; // 0xf = 15 位与掩码，把索引确定在0～15，即永远不会超出数组范围，上述算法只是一种最简单的实现。
          如果添加超过16个key-value到HashMap，数组不够用了怎么办？
            添加超过一定数量的key-value时，HashMap会在内部自动扩容，每次扩容一倍，即长度为16的数组扩展为长度32，相应地，需要重新确定hashCode()计算的索引位置。例如，对长度为32的数组计算hashCode()对应的索引，计算方式要改为：
            int index = key.hashCode() & 0x1f; // 0x1f = 31
            由于扩容会导致重新分布已有的key-value，所以，频繁扩容对HashMap的性能影响很大。
            如果我们确定要使用一个容量为10000个key-value的HashMap，更好的方式是创建HashMap时就指定容量：
          如果不同的两个key，例如"a"和"b"，它们的hashCode()恰好是相同的，
              假设"a"和"b"这两个key最终计算出的索引都是5，
              那么，在HashMap的数组中，实际存储的不是一个Person实例，
              而是一个List，它包含两个Entry，一个是"a"的映射，一个是"b"的映射：
          把不同的key具有相同的hashCode()的情况称之为哈希冲突。
          在冲突的时候，一种最简单的解决办法是用List存储hashCode()相同的key-value。
          显然，如果冲突的概率越大，这个List就越长，Map的get()方法效率就越低，
          如果两个对象不相等，则两个对象的hashCode()尽量不要相等。

          hashmap中依据key的hash值来确定value存储位置，所以一定要重写hashCode方法，
          而重写equals方法，是为了解决hash冲突，如果两个key的hash值相同，就会调用equals方法，比较key值是否相同，
          在存储时：如果equals结果相同就覆盖更新value值，如果不同就用List他们都存储起来。
          在取出来是：如果equals结果相同就返回当前value值，如果不同就遍历List中下一个元素。
          即要key与hash同时匹配才会认为是同一个key。
            JDK中源码:if(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){ops;}
        */
    }

    /*list集合内部一般都是采用equals方法进行比较的，如contains(obj),indexOf(obj)方法，以及remove(obj)等
      因此自定义的集合元素对象也要实现正确的equals方法
      我们之所以能正常放入String、Integer这些对象，是因为Java标准库定义的这些类已经正确实现了equals()方法。
    
    */
    public void collectionHandle2(){
        var list2=List.of(new Person88("xiaoZhang"),new Person88("xiaoLiu"));
        System.out.println(list2.contains(new Person88("xiaoZhang")));
        var list3=List.of(new Person99("xiaoZhang",10),new Person99("xiaoZhang",12));
        System.out.println(list3.contains(new Person99("xiaoZhang",10)));
    }
    class Person88 {
        public String name;
        public Person88(String name) {
            this.name = name;
        }
    }
    class Person99 {
        public String name;
        public int age;
        public Person99(String name,int age) {
            this.name = name;
            this.age =age;
        }
        /*equals()方法要求我们必须满足以下条件：
            自反性（Reflexive）：对于非null的x来说，x.equals(x)必须返回true；
            对称性（Symmetric）：对于非null的x和y来说，如果x.equals(y)为true，则y.equals(x)也必须为true；
            传递性（Transitive）：对于非null的x、y和z来说，如果x.equals(y)为true，y.equals(z)也为true，那么x.equals(z)也必须为true；
            一致性（Consistent）：对于非null的x和y来说，只要x和y状态不变，则x.equals(y)总是一致地返回true或者false；
            对null的比较：即x.equals(null)永远返回false。

            总结一下equals()方法的正确编写方法：
                先确定实例“相等”的逻辑，即哪些字段相等，就认为实例相等；
                用instanceof判断传入的待比较的Object是不是当前类型，如果是，继续比较，否则，返回false；
                对引用类型用Objects.equals()比较，对基本类型直接用==比较。
        */
        @Override
        public boolean equals(Object obj){            
            if(obj instanceof Person99){
                var p =(Person99)obj;
                boolean nameEquals = false;
                if(this.name==null && p.name==null)
                    nameEquals = true;
                if(this.name !=null){
                    nameEquals = this.name.equals(p.name);
                }
                //对于引用字段比较，我们使用equals()，对于基本类型字段的比较，我们使用==。
                //return nameEquals && this.age == p.age;
                //简化写法
                return Objects.equals(this.name, p.name) && this.age == p.age;
            }
            return false;
        }
        @Override
        public int hashCode() {
            /*反复使用31*h，这样做的目的是为了尽量把不同的Person实例的hashCode()均匀分布到整个int范围。
            */
            int h = 0;
            h = 31 * h + name.hashCode();
            h = 31 * h + age;
            //以上逻辑的简化方法
            int h2=Objects.hash(name, age);
            return h2;
        }
    }

    /* 集合比数组有很多方便之处：数组大小一旦初始化就固定了/数组只能通过索引存取！
       基本有三类集合类型：List可按索引存取顺序/Set不可重复/Map键值映射存取
            List接口下可能的实现ArrayList（内部采用数组方式）LinkedList（内部采用链表方式）
            Map接口下可能的实现HashMap（内部采用hash方式）
            Set接口下可能的实现HashSet（基于hashMap只是增加了验证重复逻辑），SortedSet
       集合都实现了接口和实现的分离，以及都提供了iterator迭代器模式（内部实现优化算法）iterator() hasNext() next()
       可直接用for(var item : colObj)遍历
    */
    public void collectionHandle(){ 
        var list1=List.of(1,2,3,4,5,6,6,7);
        System.out.println(list1.size());
        //list1.remove(6);//异常因为List.of为只读模式
        //System.out.println(list1.size());
        var aa=list1.toArray();
        System.out.printf("%s %s \n",aa.length,aa[6]);
        for(var lt= list1.iterator();lt.hasNext();){//集合统一实现了迭代器接口，通过设置自身特定优化的存取机制并提供 闭包yield机制
            System.out.println(lt.next());
        }
        for(var item : list1){//语法糖
            System.out.println(item);
        }
        var bb=list1.toArray(new Integer[list1.size()]);
        System.out.printf("%s %s \n",bb.length,bb[6]);

        List<Integer> list2 =new ArrayList<>();
        list2.add(1);
        list2.add(3);
        list2.add(3);
        list2.add(4);
        System.out.println(list2.size());
        list2.remove(3);
        System.out.println(list2.toString());
        System.out.printf("%s %s \n",list2.size(),list2.get(2));
        list2.remove(Integer.valueOf(3));//可按基本类型的内容移除 采用了对象的equals方法，只能移除其中之一
        System.out.println(list2.toString());
        list2.remove(Integer.valueOf(3));
        System.out.println(list2.toString());

        List<String> list3 =new ArrayList<>();
        list3.add("111");
        list3.add(0,"222");
        list3.add("222");        
        System.out.println(list3.toString());        
        list3.remove("222");//可按基本类型的内容移除 采用了对象的equals方法，只能移除其中之一
        System.out.println(list3.toString());
        list3.remove("222");//可按基本类型的内容移除 采用了对象的equals方法，只能移除其中之一
        System.out.println(list3.toString());
    }

    /* 匿名类实例，可以通过实现类继承或者实现接口的方式匿名实例化！
    */
    class AnymaousParent{
        public String aa="100";
        public void show(String val){
            System.out.println("parent---"+this.aa+" val:"+val);
        }
    }
    interface AnymaousInterface{
        default void show(String val){
            System.out.println("parentDefaultInterface---"+val);
        }
    }
    public void anonymousHandler2(){
        var ac =new AnymaousParent(){        
            {
                this.aa="200";//初始化块
            } 
        };        
        ac.show("helloa");
        var ac2 =new AnymaousParent(){
            {
                this.aa="300";//初始化块
            }
            public void show(String val){//override 父类
                System.out.println("anonymous---"+this.aa+" val:"+val);
            }
        };
        ac2.show("helloa");
        var ac3 =new AnymaousInterface(){        
            
        };        
        ac3.show("helloa");
        var ac4 =new AnymaousInterface(){        
            public void show(String val){
                System.out.println("anonymous---val:"+val);
            }
        };        
        ac4.show("helloa");
    }

    /*  如果一个类要被声明为static的，只有一种情况，就是静态内部类（俗称：内嵌类）。
        如果在外部类声明为static，程序会编译都不会过。
        1.静态内部类跟静态方法一样，只能访问静态的成员变量和方法，不能访问非静态的方法和属性，但是普通内部类可以访问任意外部类的成员变量和方法
        2.静态内部类可以声明普通成员变量和方法，而普通内部类不能声明static成员变量和方法。
        3.静态内部类可以单独初始化
    */
    /* 泛型类定义的泛型，在整个类中有效除了静态方法外。如果被方法是用，那么 
        泛型类的对象明确要操作的具体类型后，所有要操作的类型就已经固定了。
        为了让不同的方法可以操作不同类型，而且类型还不确定。那么可以将泛型定义在方法上。    
    */
    static class GenericDemo<T>{
        public void show(T t){//直接使用类的泛型，若类被某个具体类型确定后，方法中的类型也就确定了
            System.out.println("show: "+t);
        }
        public <Q> void print(Q q){//可以声明一个泛型方法，可以在类确定下，还可以泛型化
            System.out.println("print:"+q);
        }
        static <W>void method(W t){//静态方法不能使用类的泛型T
            System.out.println("method: "+t);
        }
    }
    public void genericHandle6(){
        var gc=new GenericDemo<String>();
        gc.show("hello");
        gc.print(100);
        GenericDemo.method(ZonedDateTime.ofInstant(Instant.now(),ZoneId.of("Asia/Shanghai")));
    }

    /*  使用类似<? extends Number>通配符作为方法参数时表示：
        方法内部可以调用获取Number引用的方法，例如：Number n = obj.getFirst();；
        方法内部无法调用传入Number引用的方法（null除外），例如：obj.setFirst(Number n);。
        称之为上界通配符（Upper Bounds Wildcards），即把泛型类型T的上界限定在Number了。

        <? extends Number> getFirst();
        即返回值是Number或Number的子类，因此，可以安全赋值给Number类型的变量
        允许调用get()方法获取Integer的引用；
        不允许调用set(? extends Integer)方法并传入任何Integer的引用（null除外）。
        原因还在于擦拭法。如果我们传入的p是Pair<Double>，显然它满足参数定义Pair<? extends Number>，然而，Pair<Double>的setFirst()显然无法接受Integer类型。
        这就是<? extends Number>通配符的一个重要限制：方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)。
    */
    public void genericHandle4(StaticGeneric<? extends Number,Number> p){
        Number aa= p.getName();
        System.out.println(aa);
    }
    /* 使用<? super Integer>通配符表示：
       允许调用set(? super Integer)方法传入Integer的引用；
       不允许调用get()方法获得Integer的引用。
       唯一例外是可以获取Object的引用：Object o = p.getFirst()。
       换句话说，使用<? super Integer>通配符作为方法参数，表示方法内部代码对于参数只能写，不能读。
    */
    public void genericHandle5(StaticGeneric<? super Integer,Number> p){
        p.setName(66688);
        System.out.println(p.getName());

        /*
            public class Collections {
                // 把src的每个元素复制到dest中:
                public static <T> void copy(List<? super T> dest, List<? extends T> src) {
                    ...
                    T t = dest.get(0); // compile error!
                    src.add(t); // compile error!
                }
            }
        */
        /*因为<?>通配符既没有extends，也没有super，因此：
            不允许调用set(T)方法并传入引用（null除外）；
            不允许调用T get()方法并获取T引用（只能获取Object引用）。
            换句话说，既不能读，也不能写，那只能做一些null判断：
            static boolean isNull(Pair<?> p) {
                return p.getFirst() == null || p.getLast() == null;
            }
            <?>通配符有一个独特的特点，就是：Pair<?>是所有Pair<T>的超类：
        */        
        StaticGeneric<?,?> p2 = p; // 安全地向上转型
        System.out.println(p2.getName());
        /*
            谨慎或者禁用泛型数组，由于内部的擦拭机制，可能带来很多不确定性
        */
    }

    /* JAVA 内部泛型处理机制
       完全基于编译器的 类型擦拭法 实现 Object的统一化，然后编译根据泛型类型进行安全的强类型转换！！
         编译器把类型<T>视为Object；
         编译器根据<T>实现安全的强制转型。
       约束：
         T必须是引用类型，不能是基本类型！！因为要转为Object
         无法获取泛型的Class，返回的都是不带<T>的Type
            因为T是Object，我们对Pair<String>和Pair<Integer>类型获取Class时，获取到的是同一个Class，也就是Pair类的Class
            所有泛型实例，无论T的类型是什么，getClass()返回同一个Class实例，因为编译后它们全部都是Pair<Object>。
         无法使用 p instanceof Pair<String> 
         不能实例化T类型，因为不同泛型T都会转换Object，如果可以new T()，就都是一个Object类型，这是没有意义的，是被编译禁止的
         可通过传入Class<T> cls 参数进行间接实例化
         public class Pair<T> {
            private T first;
            private T last;
            public Pair(Class<T> clazz) {
                first = clazz.newInstance();
                last = clazz.newInstance();
            }
        }

    */
    public void genericHandle3(){
        //var sg=new StaticGeneric<int>(12333);//complie error 
        var sg=new StaticGeneric<Integer,Double>(12333);//complie success
        System.out.println(sg.getClass());

        var sg2=new StaticGeneric<Integer,Double>(Double.class,new String[]{"66.88"});
        System.out.println(sg2.getSub2());        

    }

    public void genericHandle2(){
        var sg=new StaticGeneric<String,Double>("helloa");
        System.out.println(sg.getName());

        var aa= StaticGeneric.create(266,"ADFDF");
        System.out.println(aa.get(0));

    }
    
    public void genericHandle(){
        var aa=new ArrayList<Integer>();
        aa.add(Integer.valueOf(123));
        aa.add(Integer.valueOf(456));
        List<Integer> lt =aa;
        System.out.println(lt.get(0));
        System.out.println(lt.get(1));
        /*注意泛型的继承关系：可以把ArrayList<Integer>向上转型为List<Integer>（T不能变！），
          但不能把ArrayList<Integer>向上转型为ArrayList<Number>（T不能变成父类）。
        假如：我们把一个ArrayList<Integer>转型为ArrayList<Number>类型后，
        这个ArrayList<Number>就可以接受Float类型，因为Float是Number的子类。
        但是，ArrayList<Number>实际上和ArrayList<Integer>是同一个对象，
        也就是ArrayList<Integer>类型，它不可能接受Float类型， 
        所以在获取Integer的时候将产生ClassCastException。
        实际上，编译器为了避免这种错误，根本就不允许把ArrayList<Integer>转型为ArrayList<Number>。
        */

        Person44[] ps = new Person44[] {
            new Person44("Bob", 61),
            new Person44("Alice", 88),
            new Person44("Lily", 75),
        };
        Arrays.sort(ps);
        System.out.println(Arrays.toString(ps));
    }
    class Person44 implements Comparable<Person44> {//接口泛型 Comparable<T>
        String name;
        int score;
        Person44(String name, int score) {
            this.name = name;
            this.score = score;
        }
        public int compareTo(Person44 other) {
            if(this.score==other.score)
                return 0;
            else if(this.score<other.score)
                return -1;
            else return 1;
        }
        public String toString() {
            return this.name + "," + this.score;
        }
    }
    


    /*注解是一种用作标注的“元数据”
      从JVM的角度看，注解本身对代码逻辑没有任何影响，如何使用注解完全由工具决定。
       编译器用的注解：用于指示编译器动作的注解 @override 等
       类加载到内存前，工具用的注解 在加载到内存前进行可能的字节码注入或修正等
       运行时加载到内存的注解  代码运行时用到的注解如前后切面的动作注入
      注解的可以配置参数，可以有默认值，配置参数都必须为常量 基本类型 枚举 String Class及对应数组    
      
      有一些注解可以修饰其他注解，这些注解就称为元注解（meta annotation）。Java标准库已经定义了一些元注解，我们只需要使用元注解，通常不需要自己去编写元注解。
      @Target可以定义Annotation能够被应用于源码的哪些位置
      @Target({ ElementType.TYPE, ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.PARAMETER, ElementType.FIELD })
      @Retention(RetentionPolicy.RUNTIME)
        SOURCE类型的注解在编译期就被丢掉了；
        CLASS类型的注解仅保存在class文件中，它们不会被加载进JVM；
        RUNTIME类型的注解会被加载进JVM，并且在运行期可以被程序读取。
      @Inherited
      @Repeatable
    */
    @Target({ ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER })
    @Retention( RetentionPolicy.RUNTIME )    
    @Inherited
    @interface Check {//Java语言使用@interface语法来定义注解（Annotation）
        //注解的参数类似无参数方法，可以用default设定一个默认值（强烈推荐）。
        //最常用的参数应当命名为value。
        int min() default 0;
        int max() default 6;
        int value() default 3;
    }
    @Target({ ElementType.TYPE,ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER })
    @Retention( RetentionPolicy.RUNTIME )    
    @Inherited
    @interface Check2 {//Java语言使用@interface语法来定义注解（Annotation）        
        int value() default 3;
    }
    class ClassByAnnotated {
        @Check(min=0, max=100, value=55)
        public int n;
    
        @Check(value=99)
        public int p;
    
        @Check(99) // @Check(value=99)
        public int x;
    
        @Check
        public String y="ddd3333333";

        public void hello(@Check @Check2(5) String name, @Check String prefix) {
        }
    }    
    public void checkByAnnotations(){
        /* 
        Class.isAnnotationPresent(Class)
        Field.isAnnotationPresent(Class)
        Method.isAnnotationPresent(Class)
        Constructor.isAnnotationPresent(Class)

        两种方法
        Class cls = Person.class;
        if (cls.isAnnotationPresent(Report.class)) {
            Report report = cls.getAnnotation(Report.class);
            ...
        }
        Class cls = Person.class;
        Report report = cls.getAnnotation(Report.class);
        if (report != null) {
        ...
        }

        // 获取Method实例:
        Method m = ...
        // 获取所有参数的Annotation:
        Annotation[][] annos = m.getParameterAnnotations();
        // 第一个参数（索引为0）的所有Annotation:
        Annotation[] annosOfName = annos[0];
        for (Annotation anno : annosOfName) {
            if (anno instanceof Range) { // @Range注解
                Range r = (Range) anno;
            }
            if (anno instanceof NotNull) { // @NotNull注解
                NotNull n = (NotNull) anno;
            }
        }
        */
        try{
            var ca=new ClassByAnnotated();
            // 遍历所有Field:
            for (Field field : ca.getClass().getFields()) {            
                var ck = field.getAnnotation(Check.class);            
                if (ck != null) {
                    // 获取Field的值:
                    Object value = field.get(ca);
                    // 如果值是String:
                    if (value instanceof String) {
                        String s = (String) value;
                        // 判断值是否满足@Range的min/max:
                        if (s.length() < ck.min() || s.length() > ck.max()) {
                            throw new IllegalArgumentException("Invalid fieldname: " + field.getName()+"  value:"+s);
                        }
                    }
                }
            }
        }catch(Exception ex){
            System.out.println(ex.toString());
        }
        
    }    
    
    
    public void anonymousClass(){
        HashMap<String, String> map1 = new HashMap<>();
        HashMap<String, String> map2 = new HashMap<>() {}; // 匿名类!
        HashMap<String, String> map3 = new HashMap<>() {
            {
                put("A", "1");
                put("B", "2");
            }
        };
    }

    /* 基于接口可以构建一个匿名类
       匿名类与动态代理的区别是后者可以【通过其他代理类代理方法实现原接口约定的某个接口方法】！！！
    */
    public void interfaceObject(){
        var dh=new doorhandle(){
            @Override
            public boolean open(){
                System.out.println("aaaaaaaaaaaaa");
                return true;
            }
            @Override
            public boolean close(){
                System.out.println("bbbbbbbbbbbbb");
                return true;
            }
        };
        dh.open();
        dh.close();
    }
    interface doorhandle{
        boolean open();
        boolean close();
    }

    public void reflexHandle3(){
        /* 动态代理，即在运行期动态构建某个接口的实例对象
           JVM动态构建类字节码，并classloader加载进类池中！！！
           JSE中提供了标准的proxy对象，能根据传入的接口类，动态在运行期构建对应的字节码接口实例对象类
        */
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                if (method.getName().equals("morning")) {
                    System.out.println("Good morning, " + args[0]);
                    return null;
                }
                if (method.getName().equals("echo")) {                    
                    return "echo。。。。。, " + args[0];
                }
                return null;
            }
        };
        //通过代理创建动态接口实例对象，其实是一个包装类
        Hello hello = (Hello) Proxy.newProxyInstance(
            Hello.class.getClassLoader(), // 传入ClassLoader 以便区隔范围
            new Class[] { Hello.class }, // 传入要实现的接口，动态生成的字节码类会实现并包装接口方法，并传递给handler
            handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
        System.out.println(hello.echo(" hhkjkjkjkjk "));
    }
    interface Hello {
        void morning(String name);
        String echo(String name);
    }

    public void reflectHandle2(){
        try{
            /*  Method getMethod(name, Class...)：获取某个public的Method（包括父类）
                Method getDeclaredMethod(name, Class...)：获取当前类的某个Method（不包括父类）
                Method[] getMethods()：获取所有public的Method（包括父类）
                Method[] getDeclaredMethods()：获取当前类的所有Method（不包括父类）

            */
            Class stdClass = Student22.class;
            // 获取public方法getScore，参数为String:
            System.out.println(stdClass.getMethod("getScore", String.class));
            // 获取继承的public方法getName，无参数:
            System.out.println(stdClass.getMethod("getName"));
            // 获取private方法getGrade，参数为int:
            System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
            
            /*  getName()：返回方法名称，例如："getScore"；
                getReturnType()：返回方法返回值类型，也是一个Class实例，例如：String.class；
                getParameterTypes()：返回方法的参数类型，是一个Class数组，例如：{String.class, int.class}；
                getModifiers()：返回方法的修饰符，它是一个int，不同的bit表示不同的含义。
            */
            var m =stdClass.getMethod("getScore", String.class);
            System.out.println(m.getName());
            System.out.println(m.getReturnType());
            System.out.println(m.getParameterTypes().toString());
            var mm=m.getModifiers();
            System.out.println(mm);


            // 获取String substring(int)方法，参数为int:
            Method m2 = String.class.getMethod("substring", int.class);
            // 在s对象上调用该方法并获取结果:
            String r = (String) m2.invoke("Hello world", 6);
            // 打印调用结果:
            System.out.println(r);


            // 获取Integer.parseInt(String)方法，参数为String:
            Method m3 = Integer.class.getMethod("parseInt", String.class);
            // 调用该静态方法并获取结果:
            Integer n = (Integer) m3.invoke(null, "12345");
            // 打印调用结果:
            System.out.println(n);
            /*通过设置setAccessible(true)来访问非public方法；
              通过反射调用方法时，仍然遵循多态原则。
            */

            /*  Class.newInstance()的局限只能调用该类的public无参数构造方法
                为了调用任意的构造方法，Java的反射API提供了Constructor对象，它包含一个构造方法的所有信息，可以创建一个实例。
                Constructor对象和Method非常类似，不同之处仅在于它是一个构造方法，并且，调用结果总是返回实例：
                getConstructor(Class...)：获取某个public的Constructor；
                getDeclaredConstructor(Class...)：获取某个Constructor；
                getConstructors()：获取所有public的Constructor；
                getDeclaredConstructors()：获取所有Constructor。
                切记注意Constructor【总是当前类定义】的构造方法，和父类无关，因此不存在多态的问题。
            */
            // 获取构造方法Integer(int):
            Constructor cons1 = Integer.class.getConstructor(int.class);
            // 调用构造方法:
            Integer n1 = (Integer) cons1.newInstance(123);
            System.out.println(n1);
            // 获取构造方法Integer(String)
            Constructor cons2 = Integer.class.getConstructor(String.class);
            Integer n2 = (Integer) cons2.newInstance("456");
            System.out.println(n2);


            Class ci = Integer.class;
            Class cip = ci.getSuperclass();
            System.out.println(cip);
            Class cipp = cip.getSuperclass();
            System.out.println(cipp);
            System.out.println(cipp.getSuperclass());

            Class[] is = ci.getInterfaces();//要特别注意：getInterfaces()只返回当前类直接实现的接口类型，并不包括其父类实现的接口类型
            for (Class i : is) {
                System.out.println(i);
            }

            // Integer i = ?
            Integer.class.isAssignableFrom(Integer.class); // true，因为Integer可以赋值给Integer
            // Number n = ?
            Number.class.isAssignableFrom(Integer.class); // true，因为Integer可以赋值给Number
            // Object o = ?
            Object.class.isAssignableFrom(Integer.class); // true，因为Integer可以赋值给Object
            // Integer i = ?
            Integer.class.isAssignableFrom(Number.class); // false，因为Number不能赋值给Integer            

        }catch(Exception ex){
            System.out.println(ex.toString());
        }
    }
    class Student22 extends Person22 {
        public int getScore(String type) {
            return 99;
        }
        private int getGrade(int year) {
            return 1;
        }
    }
    class Person22 {
        public String getName() {
            return "Person";
        }
    }


    public void reflectHandle(){
        System.out.println(String.class);
		System.out.println("".getClass());
		System.out.println("".getClass()==String.class);
		System.out.println(Integer.class);
		System.out.println(Number.class);
		System.out.println(new Integer(123) instanceof Integer);
		System.out.println(new Integer(123) instanceof Number);
		var n=new Integer(123);
		//默认错误：error: incomparable types: Class<CAP#1> and Class<Number>
		//CAP#1 extends Integer from capture of ? extends Integer
		//需要使用不确定泛型 Class<?>
		Class<?> c = n.getClass();
		System.out.println(c == Number.class);
        try{
            //Class.forName(name)
            //内部也是通过获取caller的classLoader进行class查找和加载load，另外其默认会执行linking+initial两个环节；
            //同时强制声明了 ClassNotFoundException 异常
            //var cls = Class.forName("java.lang.String");
            printClassInfo(Class.forName("java.lang.String"));
            // 创建一个String实例:
            var cls = Class.forName("java.lang.String");
            //上述代码相当于new String()。
            //通过Class.newInstance()可以创建类实例，它的局限是：
            //只能调用public的无参数构造方法。
            //带参数的构造方法，或者非public的构造方法都无法通过Class.newInstance()被调用。
            String s = (String) cls.newInstance();
            System.out.println(s);

            Object p = new Person3("Xiao Ming");
            Class c2 = p.getClass();
            /*  Field getField(name)：根据字段名获取某个public的field（包括父类）
                Field getDeclaredField(name)：根据字段名获取当前类的某个field（不包括父类）
                Field[] getFields()：获取所有public的field（包括父类）
                Field[] getDeclaredFields()：获取当前类的所有field（不包括父类）
            */
            Field f = c2.getDeclaredField("name");
            f.getName(); // "value"
            f.getType(); // class [B 表示byte[]类型
            int m = f.getModifiers();
            Modifier.isFinal(m); 
            Modifier.isPublic(m); 
            Modifier.isProtected(m); 
            Modifier.isPrivate(m); 
            Modifier.isStatic(m); 
            /* 如果外部类调用person，会得到一个IllegalAccessException，
                这是因为name被定义为一个private字段，正常情况下，Main类无法访问Person类的private字段。
                要修复错误，可以将private改为public，
                或者，在调用Object value = f.get(p);前，先写一句：f.setAccessible(true);
                调用Field.setAccessible(true)的意思是，别管这个字段是不是public，一律允许访问。
                如果JVM运行期存在SecurityManager，那么它会根据规则进行检查，有可能阻止setAccessible(true)。
                例如，某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true)，这样可以保证JVM核心库的安全。
            反射是一种非常规的用法，使用反射，首先代码非常繁琐，其次，它更多地是给工具或者底层框架来使用，目的是在不知道目标实例任何信息的情况下，获取特定字段的值。
            */
            Object value = f.get(p);
            System.out.println(value); // "Xiao Ming"
            f.setAccessible(true);
            f.set(p, "Xiao Hong");
            System.out.println(((Person3)p).getName()); 

        }catch(Exception ex){
            System.out.println(ex.toString());
        }	       
        
    }
    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
    }
    class Person3 {
        private String name;
        public String getName(){
            return name;
        }
        public Person3(String name) {
            this.name = name;
        }
    }

    public void regexHandle(){
        System.out.println("开始正则测试"); 

        //反向引用定位的子串  $1 $2
        String s3 = "the quick brown fox jumps over the lazy dog.";
        String r3 = s3.replaceAll("\\s([a-z]{4})\\s", " <b>$1</b> ");
        System.out.println(r3);

        String s = "the quick brown fox jumps over the lazy dog.";
        Pattern p1 = Pattern.compile("\\wo\\w");
        Matcher m1 = p1.matcher(s); 
        //不直接调用 m1.matches() 方法（因为匹配整个串肯定返回false）
        //而是反复调用find()方法，在整个串中搜索能匹配上\\wo\\w规则的【子串】
        while (m1.find()) {
            String sub = s.substring(m1.start(), m1.end());
            System.out.println(sub);
        }

        //连续空格只能去掉一个作为split后留余其他作为分解部分串
        System.out.println("a b     c".split("\\s")); 
        //可以通过+1个或多个，以及[]多个不同分割符
        "a, b ;; c".split("[\\,\\;\\s]+"); // { "a", "b", "c" }

        //两个都开启非贪婪，后者作为最终领地胜利者！！
        Pattern pattern2 = Pattern.compile("(\\d+?)(\\d+?)");
        Matcher matcher2 = pattern2.matcher("1230000");
        if (matcher2.matches()) {
            System.out.println("group1=" + matcher2.group(1)); // "1230000"
            System.out.println("group2=" + matcher2.group(2)); // ""
        }

        //添加后缀? 禁止其贪婪模式 让位于后面的规则
        Pattern pattern1 = Pattern.compile("(\\d+?)(0*)");
        Matcher matcher1 = pattern1.matcher("1230000");
        if (matcher1.matches()) {
            System.out.println("group1=" + matcher1.group(1)); // "1230000"
            System.out.println("group2=" + matcher1.group(2)); // ""
        }

        //默认从左向右匹配的 * ? + {n, } 按贪婪向后匹配！！
        Pattern pattern = Pattern.compile("(\\d+)(0*)");
        Matcher matcher = pattern.matcher("1230000");
        if (matcher.matches()) {
            System.out.println("group1=" + matcher.group(1)); // "1230000"
            System.out.println("group2=" + matcher.group(2)); // ""
        }

        //分组并提取各子串
        Pattern p = Pattern.compile("(\\d{3,4})\\-(\\d{7,8})");
        Matcher m = p.matcher("010-12345678");
        if (m.matches()) {
            String g0 = m.group(0);
            String g1 = m.group(1);
            String g2 = m.group(2);
            System.out.println(g0);
            System.out.println(g1);
            System.out.println(g2);
        } else {
            System.out.println("匹配失败!");
        }
        
        //或|符号是自动分割连接的！！！
        String re0="learn\\sjava|learn\\sphp|learn\\sgo";
        System.out.println("learn java".matches(re0));
        String re = "learn\\s(java|php|go)";
        System.out.println("learn java".matches(re));
        System.out.println("learn php".matches(re));
        System.out.println("learn Go".matches(re));

        String re6 = "aaadaa\\dHHH"; //转义后才代表任意数字！！！
        System.out.println("aaadaa8HHH".matches(re6)); 

        String re5 = "a\\u548cc"; //中文字符和的Unicode编码是548c   a和c
        System.out.println("a和c".matches(re5)); 

        String re4 = "/ypw-.*\\.log"; // 转义后才代表一个普通字符.
        System.out.println("HHHHHHHHH/ypw-akjkj;afd.logHHHHHHHHHH".replaceAll(re4, "999999"));
        System.out.println("ffffffffff/ypw-akjkj;afd.log000000000".matches(re4)); //精确匹配 从头到尾

        String regex = "20\\d\\d";
        System.out.println("2019".matches(regex)); // true
        System.out.println("2100".matches(regex)); // false

        String re1 = "abc";
        System.out.println("abc".matches(re1));
        System.out.println("Abc".matches(re1));
        System.out.println("abcd".matches(re1));

        String re2 = "a\\.c"; // 对应的正则是a\.c  转义后代表一个普通字符.
        System.out.println("a.c".matches(re2));
        System.out.println("a-c".matches(re2));
        System.out.println("a&c".matches(re2));

        String re3 = "a.c"; // 对应的正则是a.c .号代表任意字符
        System.out.println("a.c".matches(re3));
        System.out.println("a-c".matches(re3));
        System.out.println("a&c".matches(re3));        
    }
    /**************牢记一点，绝对时间时刻****时区时间只是对绝对时间时的转换，也不要和零时区的时间混淆********************/
    /*时区时间
      LocalDateTime总是表示本地日期和时间，要表示一个带时区的日期和时间，我们就需要ZonedDateTime。
      可以简单地把ZonedDateTime理解成LocalDateTime加ZoneId。
    */
    public void datetimeNewAbout2(){ 
        //*****注意到LocalDateTime无法与时间戳进行转换，因为LocalDateTime没有时区，无法确定某一时刻。

        // 获取默认时区下【当前世界时】的区域世界格式内容
        ZonedDateTime zbj = ZonedDateTime.now(); 
        // 获取某个时区下【当前世界时】的区域世界格式内容
        ZonedDateTime zny = ZonedDateTime.now(ZoneId.of("America/New_York")); 
        System.out.println(zbj);
        System.out.println(zny);//表达同一时刻的不同时区下的展示，可能有毫秒秒级别的差别，但总体涵义是这个

        LocalDateTime ldt = LocalDateTime.of(2019, 9, 15, 15, 16, 17);
        //这是在本地时区下的这个时间对应的时间时
        ZonedDateTime zbj2 = ldt.atZone(ZoneId.systemDefault());
        //这是在另一个地时区下的这个时间对应的时间时,总体上来说就是表达了 不同时区下相同日期时间对应的【两个不同】的世界时！！
        ZonedDateTime zny2 = ldt.atZone(ZoneId.of("America/New_York"));
        System.out.println(zbj2);
        System.out.println(zny2);

        // 以中国时区获取当前世界时下的时间内容:
        ZonedDateTime zbj3 = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
        // 转换为相同世界时刻的对应的纽约当地时间:
        ZonedDateTime zny3 = zbj3.withZoneSameInstant(ZoneId.of("America/New_York"));
        System.out.println(zbj3);
        System.out.println(zny3);

        //通过带时区的时间，转换为对应时区下的时间，切记【不是指当前系统所在时区】ZoneId.systemDefault()
        //其实就是直接去掉了时区信息而已
        LocalDateTime ldt4 = zny3.toLocalDateTime();
        System.out.println(ldt4);

        ZonedDateTime zdt = ZonedDateTime.now();
        var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm ZZZZ");
        System.out.println(formatter.format(zdt));
        var zhFormatter = DateTimeFormatter.ofPattern("yyyy MMM dd EE HH:mm", Locale.CHINA);
        System.out.println(zhFormatter.format(zdt));
        var usFormatter = DateTimeFormatter.ofPattern("E, MMMM/dd/yyyy HH:mm", Locale.US);
        System.out.println(usFormatter.format(zdt));

        Instant now = Instant.now();//System.currentTimeMillis()
        System.out.println(now.getEpochSecond()); // 秒
        System.out.println(now.toEpochMilli()); // 毫秒

        // 以指定时间戳创建Instant:
        //对于某一个时间戳，给它关联上指定的ZoneId，就得到了ZonedDateTime，继而可以获得了对应时区的LocalDateTime
        Instant ins = Instant.ofEpochSecond(1568568760);
        ZonedDateTime zdt9 = ins.atZone(ZoneId.systemDefault());
        System.out.println(zdt9); // 2019-09-16T01:32:40+08:00[Asia/Shanghai]

        //旧api的.toInstant() 和 .toZoneId() 作为中转到新的api
        // Date -> Instant:
        Instant ins1 = new Date().toInstant();
        // Calendar -> Instant -> ZonedDateTime:
        Calendar calendar = Calendar.getInstance();
        Instant ins2 = calendar.toInstant();
        ZonedDateTime zdt10 = ins2.atZone(calendar.getTimeZone().toZoneId());

        /*最佳实践
         在数据库中，需要存储的最常用的是时刻（Instant），
         因为有了时刻信息，就可以根据用户自己选择的时区，显示出正确的本地时间。
         所以，最好的方法是直接用长整数long表示，在数据库中存储为BIGINT类型。        
        */
        long ts = 1574208900000L;
        System.out.println(timestampToString(ts, Locale.CHINA, "Asia/Shanghai"));
        System.out.println(timestampToString(ts, Locale.US, "America/New_York"));

    }
    static String timestampToString(long epochMilli, Locale lo, String zoneId) {
        Instant ins = Instant.ofEpochMilli(epochMilli);
        DateTimeFormatter f = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT);
        return f.withLocale(lo).format(ZonedDateTime.ofInstant(ins, ZoneId.of(zoneId)));
    }
    //j8以后新 api 
    //月份从1开始，星期从1-7 不变类型类似string
    //LocalDatetime localDate localTime
    //instant  Duration ZoneId ZoneOffset ZoneDateTime
    //SimpleDateFormat DatetimeFormatter
    public void datetimeNewAbout(){
        LocalDate d = LocalDate.now(); // 当前日期 以当前默认时区 系统设置的本地时区
        LocalTime t = LocalTime.now(); // 当前时间
        LocalDateTime dt = LocalDateTime.now(); // 当前日期和时间
        System.out.println(d); // 严格按照ISO 8601格式打印
        System.out.println(t); // 严格按照ISO 8601格式打印
        System.out.println(dt); // 严格按照ISO 8601格式打印
        LocalDateTime dt1 = LocalDateTime.now(); // 当前日期和时间
        LocalDate d1 = dt.toLocalDate(); // 转换到当前日期
        LocalTime t1 = dt.toLocalTime(); // 转换到当前时间
        System.out.println(d1); // 严格按照ISO 8601格式打印
        System.out.println(t1); // 严格按照ISO 8601格式打印
        System.out.println(dt1); // 严格按照ISO 8601格式打印

        // 指定日期和时间:
        LocalDate d2 = LocalDate.of(2019, 11, 30); // 2019-11-30, 注意11=11月
        LocalTime t2 = LocalTime.of(15, 16, 17); // 15:16:17
        LocalDateTime dt2 = LocalDateTime.of(2019, 11, 30, 15, 16, 17);
        LocalDateTime dt3 = LocalDateTime.of(d2, t2);
        System.out.println(d2); // 严格按照ISO 8601格式打印
        System.out.println(t2); // 严格按照ISO 8601格式打印
        System.out.println(dt2); // 严格按照ISO 8601格式打印
        System.out.println(dt3); // 严格按照ISO 8601格式打印

        /* 注意ISO 8601规定的日期和时间分隔符是T。标准格式如下：
            日期：yyyy-MM-dd
            时间：HH:mm:ss
            带毫秒的时间：HH:mm:ss.SSS
            日期和时间：yyyy-MM-dd'T'HH:mm:ss
            带毫秒的日期和时间：yyyy-MM-dd'T'HH:mm:ss.SSS        
        */
        LocalDateTime dt4 = LocalDateTime.parse("2019-11-19T15:16:17");
        LocalDate d4 = LocalDate.parse("2019-11-19");
        LocalTime t4 = LocalTime.parse("15:16:17");
        
        // 自定义格式化:
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        System.out.println(dtf.format(LocalDateTime.now()));
        // 用自定义格式解析:
        System.out.println(LocalDateTime.parse("2019/11/30 15:16:17", dtf));

        LocalDateTime dt5 = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
        System.out.println(dt);
        // 加5天减3小时:
        LocalDateTime dt6 = dt5.plusDays(5).minusHours(3);
        System.out.println(dt2); // 2019-10-31T17:30:59
        // 减1月:
        LocalDateTime dt7 = dt6.minusMonths(1);
        System.out.println(dt7); // 2019-09-30T17:30:59

        LocalDateTime dt8 = LocalDateTime.of(2019, 10, 26, 20, 30, 59);
        System.out.println(dt8);
        // 日期变为31日:
        LocalDateTime dt9 = dt8.withDayOfMonth(31);
        System.out.println(dt9); // 2019-10-31T20:30:59
        // 月份变为9: 自动调整为30号！！！！最后一天
        LocalDateTime dt10 = dt9.withMonth(9);
        System.out.println(dt10); // 2019-09-30T20:30:59

        // 本月第一天0:00时刻:
        LocalDateTime firstDay = LocalDate.now().withDayOfMonth(1).atStartOfDay();
        System.out.println(firstDay);
        // 本月最后1天:
        LocalDate lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
        System.out.println(lastDay);
        // 下月第1天:
        LocalDate nextMonthFirstDay = LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println(nextMonthFirstDay);

        // 本月第1个周一:
        LocalDate firstWeekday = LocalDate.now().with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
        System.out.println(firstWeekday);

        //Duration和Period的表示方法也符合ISO 8601的格式，
        //它以P...T...的形式表示，P...T之间表示日期间隔，T后面表示时间间隔。
        //如果是PT...的格式表示仅有时间间隔。
        LocalDateTime start = LocalDateTime.of(2019, 11, 19, 8, 15, 0);
        LocalDateTime end = LocalDateTime.of(2020, 1, 9, 19, 25, 30);
        Duration dra = Duration.between(start, end);
        System.out.println(dra); // PT1235H10M30S        
        Period prd = LocalDate.of(2019, 11, 19).until(LocalDate.of(2020, 1, 9));
        System.out.println(prd); // P1M21D

        Duration dra1 = Duration.ofHours(10); // 10 hours
        System.out.println(dra1);
        Duration dra2 = Duration.parse("P1DT2H3M"); // 1 day, 2 hours, 3 minutes
        System.out.println(dra2);
        

    }
    //java.util.Date java.util.TimeZone java.util.Canlendar
    public void datetimeOldAbout(){
        ////获取当前时间戳
        System.out.println(System.currentTimeMillis());
        // 获取当前绝对epoch time 时间:其实内部是一个long类型的epoch time 毫秒时间戳
        Date date = new Date();
        System.out.println(date.getYear() + 1900); // 必须加上1900
        System.out.println(date.getMonth() + 1); // 0~11，必须加上1
        System.out.println(date.getDate()); // 1~31，不能加1
        // 转换为String:
        System.out.println(date.toString());
        // 转换为GMT时区:
        System.out.println(date.toGMTString());
        // 转换为本地时区:
        System.out.println(date.toLocaleString());
		
		var sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println(sdf.format(date));
		
		// 当前时间:
        Calendar c = Calendar.getInstance();
        // 清除所有:
        c.clear();
        // 设置2019年:
        c.set(Calendar.YEAR, 2019);
        // 设置9月:注意8表示9月:
        c.set(Calendar.MONTH, 8);
        // 设置2日:
        c.set(Calendar.DATE, 2);
        // 设置时间:
        c.set(Calendar.HOUR_OF_DAY, 21);
        c.set(Calendar.MINUTE, 22);
        c.set(Calendar.SECOND, 23);
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(c.getTime()));
        // 2019-09-02 21:22:23
		// 清除所有:
        c.clear();
        // 设置为北京时区: 其实就是预先定义一个时区，然后后面定义时间的时候进行时间戳的转换
        c.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        // 设置年月日时分秒:此时内部存储时间戳的时候会进行以上面时区的转换后进行存储为世界时 0时区时刻
        c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);
        // 显示时间:
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //将时间戳表达的世界时即0时区时间戳，转换为某个时区的时间
        //即时间戳epoch time是绝对的以无时区的概念表达了绝对时刻
        //    将绝对时，转换为某个时区时间只能通过SimpleDateFormat在显示的时候完成。
        sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(sdf.format(c.getTime()));        
		
		TimeZone tzDefault = TimeZone.getDefault(); // 当前时区
        TimeZone tzGMT9 = TimeZone.getTimeZone("GMT+09:00"); // GMT+9:00时区
        TimeZone tzNY = TimeZone.getTimeZone("America/New_York"); // 纽约时区
        System.out.println(tzDefault.getID()); // Asia/Shanghai
        System.out.println(tzGMT9.getID()); // GMT+09:00
        System.out.println(tzNY.getID()); // America/New_York

        // 清除所有:
        c.clear();
        // 设置年月日时分秒:
        c.set(2019, 10 /* 11月 */, 20, 8, 15, 0);
        // 加5天并减去2小时:
        c.add(Calendar.DAY_OF_MONTH, 5);
        c.add(Calendar.HOUR_OF_DAY, -2);
        // 显示时间:
        sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = c.getTime();
        System.out.println(sdf.format(d));
        // 2019-11-25 6:15:00
    }

    /*基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型：
        带符号整数类型：byte 1，short 2，int 4，long 8
        浮点数类型：float 4，double 8
        字符类型：char 2 
        布尔类型：boolean     
        基本类型的变量是“持有”某个数值，引用类型的变量是“指向”某个对象；   
    */
    public void baseVar1(){
        int i3 = 2_000_000_000; // 加下划线更容易识别
        System.out.println(i3);
        int i4 = 0xff0000; // 0x开头十六进制表示的16711680
        System.out.println(i4);
        int i5 = 0b1000000000; // 0b开头二进制表示的512
        System.out.println(i5);
        long l = 9000000000000000000L; // long型的结尾需要加L
        System.out.println(l);
    }
    /*
        浮点类型的数就是小数，因为小数用科学计数法表示的时候，小数点是可以“浮动”的，
        如1234.5可以表示成12.345x102，也可以表示成1.2345x103，所以称为浮点数。
        对于float类型，需要加上f后缀。
    */
    public void baseVar2(){
        final var PI = 3.14f;
        System.out.println(PI);
        float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
        System.out.println(f2);
        double d = 1.79e308;
        System.out.println(d);
        double d2 = -1.79e308;
        System.out.println(d2);
        double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324
        System.out.println(d3);
    }
    public void baseVar3(){
        char a = 'A';
        char zh = '中';
        System.out.println(a);
        System.out.println(zh);
    }
    public void baseCompute(){
        int x = 12345 / 67; // 184
        System.out.println(x);        
        int y = 12345 % 67; // 12345÷67的余数是17
        System.out.println(y);        
        //要特别注意，整数由于存在范围限制，如果计算结果超出了范围，就会产生溢出，而溢出不会出错，却会得到一个奇怪的结果
        int x1 = 2147483640;
        int y1 = 15;
        int sum = x1 + y1;
        System.out.println(sum); // -2147483641
        //整数的除法对于除数为0时运行时将报错，但编译不会报错。
        //System.out.println(123/0);
    }
    /*
        对byte和short类型进行移位时，会首先转换为int再进行位移。
        仔细观察可发现，左移实际上就是不断地×2，右移实际上就是不断地÷2。
        浮点数运算和整数运算相比，只能进行加减乘除这些数值计算，不能做位运算和移位运算。
    */
    public void bitShift(){
        //左移
        int n = 7;       // 00000000 00000000 00000000 00000111 = 7     
        System.out.println(n << 28);// 01110000 00000000 00000000 00000000 = 1879048192
        //左移29位时，由于最高位变成1，因此结果变成了负数。        
        System.out.println(n << 29);// 11100000 00000000 00000000 00000000 = -536870912
        //右移 保留高位符号
        n = -536870912;
        System.out.println(n >> 1);  // 11110000 00000000 00000000 00000000 = -268435456
        System.out.println(n >> 2); // 11111000 00000000 00000000 00000000 = -134217728
        //切记负数的高位连续的全是1
        System.out.println(n >>28);  // 11111111 11111111 11111111 11111110 = -2
        System.out.println(n >>29);  // 11111111 11111111 11111111 11111111 = -1

        //右移 不管符号位，右移后高位总是补0
        System.out.println(n >>> 1);  // // 01110000 00000000 00000000 00000000 = 1879048192
        System.out.println(n >>> 2); // 00111000 00000000 00000000 00000000 = 939524096
        System.out.println(n >>>29);  // 00000000 00000000 00000000 00000111 = 7
        System.out.println(n >>>31);  // 00000000 00000000 00000000 00000001 = 1
    }
    /*
        浮点数0.1在计算机中就无法精确表示，因为十进制的0.1换算成二进制是一个无限循环小数，
        很显然，无论使用float还是double，都只能存储一个0.1的近似值。但是，0.5这个浮点数又可以精确地表示。
        因为浮点数常常无法精确表示，因此，浮点数运算会产生误差：
        由于浮点数存在运算误差，所以比较两个浮点数是否相等常常会出现错误的结果。
        正确的比较方法是判断两个浮点数之差的绝对值是否小于一个很小的数：
    */
    public void floatCompute(){
        double x = 1.0 / 10;
        double y = 1 - 9.0 / 10;
        // 观察x和y是否相等:
        System.out.println(x);
        System.out.println(y);
        // 比较x和y是否相等，先计算其差的绝对值:
        double r = Math.abs(x - y);
        // 再判断绝对值是否足够小:
        if (r < 0.00001) {
            System.out.println("可以认为相等");
        } else {
            System.out.println("可以认为不相等");
        }
        //一般情况下，整数和浮点数混合运算，整型自动提升为浮点数
        //但也会存在两个整数的运算不会出现自动提升的情况。例如：
        double d = 1.2 + 24 / 5; // 5.2
        System.out.println(d);
        //可以将浮点型强制转为整型（直接丢弃其小数部分），但超出范围后将始终返回整型的最大值；
        int n1 = (int) 12.3; // 12
        System.out.println(n1);
        int n2 = (int) 12.7; // 12
        System.out.println(n2);
        n2 = (int) -12.7; // -12
        System.out.println(n2);
        //通过整型化，四舍五入
        int n3 = (int) (12.7 + 0.5); // 13
        System.out.println(n3);
        int n4 = (int) 1.2e20; // 2147483647
        System.out.println(n4);
        //溢出不报异常
        double d1 = 0.0 / 0; // NaN
        System.out.println(d1);
        double d2 = 1.0 / 0; // Infinity
        System.out.println(d2);
        double d3 = -1.0 / 0; // -Infinity
        System.out.println(d3);
    }
    /*
        Java在内存中总是使用Unicode表示字符，所以，一个英文字符和一个中文字符都用一个char类型表示，
        它们都占用两个字节。要显示一个字符的Unicode编码，只需将char类型直接赋值给int类型即可
    */
    public void charStrHandle(){
        int n1 = 'A'; // 字母“A”的Unicodde编码是65
        int n2 = '中'; // 汉字“中”的Unicode编码是20013
        System.out.println(n1);
        System.out.println(n2);
        // 注意是十六进制:
        char c3 = '\u0041'; // 'A'，因为十六进制0041 = 十进制65
        System.out.println(c3);
        char c4 = '\u4e2d'; // '中'，因为十六进制4e2d = 十进制20013
        System.out.println(c4);

        String s = "ABC\n\u4e2d\u6587"; // 包含6个字符: A, B, C, 换行符, 中, 文
        System.out.println(s);
        //Java的编译器对字符串做了特殊照顾，可以使用+连接任意字符串和其他数据类型
        //如果用+连接字符串和其他数据类型，会将其他数据类型先自动转型为字符串，再连接
        int age = 25;
        s = "age is " + age;
        System.out.println(s);
        //从Java 13开始，字符串可以用"""..."""表示多行字符串（Text Blocks）了。举个例子：
        // s = """
        //            SELECT * FROM
        //              users
        //            WHERE id > 100
        //            ORDER BY name DESC
        //            """;
        // System.out.println(s);
        //原来的字符串"hello"还在，只是我们无法通过变量s2访问它而已。因此，字符串的不可变是指字符串内容不可变。
        String s2 = "hello";
        String t = s2;
        s2 = "world";
        System.out.println(t); // t是"hello"还是"world"?

        int a = 72;
        int b = 105;
        int c = 65281;
        /* 类型转换，是告知内部编译器的处理，内存安排和标识类型，以按照标识进行相应处理反应
           整型类型int强制转换为字符类型char ==>H
           用+连接字符串("")和其他数据类型,会将其他数据类型先转型为字符串类型,再连接
        */
        s2 ="" + (char)a + (char)b + (char)c;
        System.out.println(s2);
    }
    /* Java的数组有几个特点：
        数组所有元素初始化为默认值，整型都是0，浮点型是0.0，布尔型是false；
        数组一旦创建后，大小就不可改变。
        数组是引用类型
    */
    public void arrayHandle(){        
        int[] ns = new int[5];
        ns[0] = 68;
        ns[1] = 79;
        int[] ns2 = new int[] { 68, 79, 91, 85, 62 };
        System.out.println(ns2.length); // 编译器自动推算数组大小为5
        int[] ns3 = { 68, 79, 91, 85, 62 };
        System.out.println(ns3.length); // 编译器自动推算数组大小为5
    }
    /**
     * 格式化 控制台输入
     */
    public void consoleInputFormat(){        
        double d = 3.1415926;
        System.out.printf("%.2f\n", d); // 显示两位小数3.14
        System.out.printf("%.4f\n", d); // 显示4位小数3.1416
        int n = 12345000;
        var out =String.format("n=%d, hex=%08x", n, n);// 注意，两个%占位符必须传入两个数
        System.out.printf("out=%s", out); 
        System.out.println("");

        //java.util.Scanner scanner = new java.util.Scanner(System.in); // 创建Scanner对象
        System.out.print("Input your name: "); // 打印提示
        //String name = scanner.nextLine(); // 读取一行输入并获取字符串
        System.out.print("Input your age: "); // 打印提示
        //int age = scanner.nextInt(); // 读取一行输入并获取整数
        //System.out.printf("Hi, %s, you are %d\n", name, age); // 格式化输出
        System.out.println("");
    }
    /*
        要注意浮点数判断相等不能直接用==运算符；Math.abs()<=0.00001
        字符串引用类型判断内容相等要使用equals()，注意避免NullPointerException。
        obj1.equals(obj2)  obj1不能为Null，可以使用肯定不是null的obj作为obj1        
    */
    public void equalHandle(String s1){        
        if (s1 != null && s1.equals("hello")) {
            System.out.println("hello");
        }
        if ("hello".equals(s1)) {
            System.out.println("hello2222222222222");
        }
    }
    /*
        switch的计算结果必须是整型、字符串或枚举类型；
        case 可以用{}进行作用域限定处理，高版本甚至可以写出lamda表达式方式
        字符串是按内容判定 equals
        从Java 14开始，switch语句正式升级为表达式，不再需要break，并且允许使用yield返回值。
    */
    public void switchHandle(){
        String fruit = "apple";
        switch (fruit) {
            case "apple":{
                var aa=10;
                System.out.println("Selected "+aa);               
            }                
            case "pear":{
                var aa=22;
                System.out.println("Selected "+aa);
                break;
            }                
            case "mango":
                System.out.println("Selected 3");
            default:
                System.out.println("Not selected");
        }
        int[] ns = { 11, 4, 19, 16, 25 };
        for (int n : ns) {
            System.out.println(n);
        }
        System.out.println(Arrays.toString(ns));
        Arrays.sort(ns);
        System.out.println(Arrays.toString(ns));
    }
    /*
        可变参数  其实是个数组参数,可变参数必须为最后一个参数项
        但是可以方便调用方，随意变动参数个数 0个 和 多个
        另外，也可以保证数组参数不为null
        切记基本类型的值复制模式和引用类型的同指向问题！
    */
    public void argsFunction(int aa,String...args){
        System.out.println(Arrays.toString(args));
    }

    /*
      final 字段可以只在构造时一次性赋值
    */
    abstract class Person {
        protected final String name;//此处只声明不初始化
        protected int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        public void run() {
            System.out.println("Person.run");
        }

        public abstract void run22();

        public String getAbc(){
            return "";
        }
        public void setAbc(String val){

        }
    }
    /*
        这是因为在Java中，任何class的构造方法，【第一行】语句必须是调用父类的构造方法。
        如果没有明确地调用父类的构造方法，编译器会帮我们自动加一句super();
        但是，如果Person类并没有无参数的构造方法，因此，编译失败。
        解决方法是调用Person类存在的某个构造方法
        子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的，不是继承的。
        只要某个class没有final修饰符，那么任何类都可以从该class继承
        子类引用父类的字段时，可以用super.fieldName

        继承是is关系，组合是has关系。
        Student不应该从Book继承，而应该从Person继承。
        Student是Person的一种，它们是is关系，而Student并不是Book。实际上Student和Book的关系是has关系
        具有has关系不应该使用继承，而是使用组合，即Student可以持有一个Book实例：
    */
    class Student extends Person {
        protected int score;
    
        public Student(String name, int age, int score) {
            super(name, age); // 调用父类的构造方法Person(String, int)
            this.score = super.age;
        }
        /*
        加上@Override可以让编译器帮助检查是否进行了正确的覆写。
        希望进行覆写，但是不小心写错了方法签名，编译器会报错。
        但是@Override不是必需的。
        */
        @Override
        public void run() {
            System.out.println("Student.run");
        }

        // 显示更有意义的字符串:
        @Override
        public String toString() {
            return "Person:name=" + name;
        }

        // 比较是否相等:
        @Override
        public boolean equals(Object o) {
            // 当且仅当o为Person类型:
            if (o instanceof Person) {
                Person p = (Person) o;
                // 并且name字段相同时，返回true:
                return this.name.equals(p.name);
            }
            return false;
        }

        // 计算hash:
        @Override
        public int hashCode() {
            return this.name.hashCode();
        }
        /*  面向抽象编程的本质就是：
                上层代码只定义规范（例如：abstract class Person）；
                不需要子类就可以实现业务逻辑（正常编译）；
                具体的业务逻辑由不同的子类实现，调用者并不关心。
        */        
        @Override
        public void run22() {
            System.out.println("Student.run222222222222222222");  
        }
    }
    /*
        instanceof实际上判断一个变量所指向的实例是否是指定类型，或者这个类型的子类。
        如果一个引用变量为null，那么对任何instanceof的判断都为false。
    */
    public void instanceofHandle(){        
        var s =new Student("ss", 22, 100);
        if(s instanceof Person){
            System.out.println("Student 子类可安全 instanceof ");
        }
        if(null instanceof Person){
            System.out.println("null 可安全 instanceof 1111");
        }else{
            System.out.println("null 可安全 instanceof 2222");
        }
    }
    /*
    Java的实例方法调用是基于运行时的实际类型的动态调用，而非变量的声明类型。
    多态是指，针对某个类型的方法调用，其真正执行的方法取决于运行时期实际类型的方法
    */
    public void polymorphicHandle(){
        Person p = new Student("aa",12,100);
        p.run(); // 应该打印Person.run还是Student.run?        
    }
    /*
        接口可以声明缺省方法，目的是为了在后期扩展了接口的方法后，对已有的继承于此的子类没有影响；
        即原有的子类可以默认采用接口中的新方法的默认实现，只在必要时在override
        default方法和抽象类的普通方法是有所不同的。
        因为interface没有字段，default方法无法访问字段，而抽象类的普通方法可以访问实例字段。
    */
    interface Person2 {
        public static final int MALE = 1;
        // 编译器会自动加上public statc final:
        int FEMALE  = 333;
        String getName();
        default void run() {
            System.out.println(getName() + " 接口的 run");
        }
        default void run33() {
            System.out.println(getName() + " 接口的 run3333");
        }
    }    
    class Student2 implements Person2 {
        private String name;    
        public Student2(String name) {
            this.name = name;
        }    
        public String getName() {
            return this.name;
        }
        @Override
        public void run33(){
            System.out.println(getName() + " 子类的 run3333");
        }
    }
    public void defaultMethodHandle(){
        Person2 p = new Student2("aa");
        p.run(); 
        p.run33();  
    }

    public void stringHandle(){
        System.out.println("　　　 abc中国　　　".trim());
        var aa="\u3000Hello\u3000";
        System.out.println(aa);
        System.out.println(aa.strip());
        "".isEmpty(); // true，因为字符串长度为0
        "  ".isEmpty(); // false，因为字符串长度不为0
        "  \n".isBlank(); // true，因为只包含空白字符
        " Hello ".isBlank(); // false，因为包含非空白字符
        String s = "A,B,C,D";
        String[] ss = s.split(","); // {"A", "B", "C", "D"}
        for(String a : ss){
            System.out.println(a);
        }
        String[] arr = {"A", "B", "C"};
        System.out.println(String.join("***", arr));// "A***B***C"

        System.out.println(String.valueOf(123)); // "123"
        System.out.println(String.valueOf(45.67)); // "45.67"
        System.out.println(String.valueOf(true)); // "true"
        System.out.println(String.valueOf(new Object())); // 类似java.lang.Object@636be97c

        int n1 = Integer.parseInt("123"); // 123
        System.out.println(n1);
        int n2 = Integer.parseInt("ff", 16); // 按十六进制转换，255
        System.out.println(n2);
        /*
        通过new String(char[])创建新的String实例时，它并不会直接引用传入的char[]数组，而是会复制一份，
        所以，修改外部的char[]数组不会影响String实例内部的char[]数组，因为这是两个不同的数组。
        从String的不变性设计可以看出，如果传入的对象有可能改变，我们需要复制而不是直接引用。
        */
        char[] cs = "Hello".toCharArray(); // String -> char[]
        String s2 = new String(cs); // char[] -> String
        System.out.println(s2);
        /*
        在循环中，每次循环都会创建新的字符串对象，然后扔掉旧的字符串。
        这样，绝大部分字符串都是临时对象，不但浪费内存，还会影响GC效率。
        为了能高效拼接字符串，Java标准库提供了StringBuilder，它是一个可变对象，可以预分配缓冲区，
        这样，往StringBuilder中新增字符时，不会创建新的临时对象
        对于普通的字符串+操作，并不需要我们将其改写为StringBuilder，
        因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。
        在运行期，StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。
        */
        StringBuilder sb = new StringBuilder(1024);
        for (int i = 0; i < 1000; i++) {
            sb.append(',');
            sb.append(i);
        }
        System.out.println(sb.toString());
    }

    public void boxingHandle(){
        int i = 100;        
        // 通过静态方法valueOf(int)创建Integer实例:
        Integer n2 = Integer.valueOf(i);
        System.out.println(n2);
        // 通过静态方法valueOf(String)创建Integer实例:
        Integer n3 = Integer.valueOf("100");
        System.out.println(n3.intValue());
        //所有的包装类型都是不变类。因此，一旦创建了Integer对象，该对象就是不变的。
        //对两个Integer实例进行比较要特别注意：绝对不能用==比较，因为Integer是引用类型，必须使用equals()比较：
        Integer x = 127;
        Integer y = 127;
        Integer m = 99999;
        Integer n = 99999;
        System.out.println("x == y: " + (x==y)); // true
        System.out.println("m == n: " + (m==n)); // false
        System.out.println("x.equals(y): " + x.equals(y)); // true
        System.out.println("m.equals(n): " + m.equals(n)); // true
        //byte是有符号整型，范围是-128~+127，但如果把byte看作无符号整型，它的范围就是0~255。
        //我们把一个负的byte按无符号整型转换为int：
        //通过升高类型，将有符号转为无符号数
        byte x1 = -1;
        byte y1 = 127;
        System.out.println(Byte.toUnsignedInt(x1)); // 255
        System.out.println(Byte.toUnsignedInt(y1)); // 127
    }

    public void beanInfoHandle(){
        try{
            BeanInfo info = Introspector.getBeanInfo(Person.class);
            for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
                System.out.println(pd.getName());
                System.out.println("  " + pd.getReadMethod());
                System.out.println("  " + pd.getWriteMethod());
            }
        }catch(Exception ex){

        }        
    }
    /*
    枚举就是特殊的类 一堆静态字段
    name()返回常量名，例如：String s = Weekday.SUN.name(); // "SUN"
    ordinal()返回定义的常量的顺序，从0开始计数，例如：int n = Weekday.MON.ordinal(); // 1
    改变枚举常量定义的顺序就会导致ordinal()返回值发生变化。
    判断枚举常量的名字，要始终使用name()方法，绝不能调用toString()！
    */
    enum Weekday {
        MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");    
        public final int dayValue;
        private final String chinese;    
        private Weekday(int dayValue, String chinese) {
            this.dayValue = dayValue;
            this.chinese = chinese;
        }    
        @Override
        public String toString() {
            return this.chinese;
        }
    }    
    public void  enumHandle(){
        Weekday day = Weekday.SUN;
        if (day.dayValue == 6 || day.dayValue == 0) {
            System.out.println("Today is " + day + ". Work at home!");
        } else {
            System.out.println("Today is " + day + ". Work at office!");
        }
    }

    public void mathToolHandle(){
        //Random用来创建伪随机数。所谓伪随机数，是指只要给定一个初始的种子，产生的随机数序列是完全一样的。
        //如果不给定种子，就使用系统当前时间戳作为种子，因此每次运行时，种子不同，得到的伪随机数序列就不同。
        double x = Math.random(); // x的范围是[0,1)
        double min = 10;
        double max = 50;
        double y = x * (max - min) + min; // y的范围是[10,50)
        long n = (long) y; // n的范围是[10,50)的整数
        System.out.println(y);
        System.out.println(n);
        //需要使用安全随机数的时候，必须使用SecureRandom，绝不能使用Random！
        //SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。这个种子是通过CPU的热噪声、读写磁盘的字节、网络流量等各种随机事件产生的“熵”。
        SecureRandom sr = null;
        try {
            sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
        } catch (NoSuchAlgorithmException e) {
            sr = new SecureRandom(); // 获取普通的安全随机数生成器
        }
        byte[] buffer = new byte[16];
        sr.nextBytes(buffer); // 用安全随机数填充buffer
        System.out.println(Arrays.toString(buffer));

    }

    void tmpRecure(){        
        tmpRecure();
    }
    public void throwableHandle(){
        try{            
            //tmpRecure();
            Class.forName("aaaabbbb");
        }catch(Exception ex){
            try{
                throw new BizException("友好信息two222222","detail two222222",ex);
            }catch(Throwable tb22){
                try{
                    throw new Exception("third333333",tb22);
                }catch(Throwable tb333){                    
                    System.out.println("Exception");          
                    var epi = BizException.GetExceptionInfo(tb333); 
                    System.out.println(epi.getFriendMsg());  
                    BizException.outExptionLog(tb333,"main-err");
                    System.out.println("Exception66666");  
                }         
            }            
        }catch(Throwable ta){
            System.out.println("Throwable");
            ta.printStackTrace();
            System.out.println(ta.toString());
            System.out.println(ta.getMessage());
            System.out.println(ta.getStackTrace());
        }
    }

    public Optional<String> readFromFile(String file) {
        if(file.isEmpty())
            return Optional.empty();
        else return Optional.of("66666666666");
    }
    public void testOptional() {
        var res = readFromFile("aaa");
        if(res.isPresent()){
            var s=res.orElse("other");
            System.out.println(s);
        }            
        else System.out.println("wu wu wu");
    }

}
